Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Lib/test/test_bytes.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,6 @@ def test_hex(self):
self.assertEqual(self.type2test(b"\x1a\x2b\x30").hex(), '1a2b30')
self.assertEqual(memoryview(b"\x1a\x2b\x30").hex(), '1a2b30')

@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: Unexpected keyword argument sep
def test_hex_separator_basics(self):
three_bytes = self.type2test(b'\xb9\x01\xef')
self.assertEqual(three_bytes.hex(), 'b901ef')
Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_memoryview.py
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,6 @@ def test_memoryview_hex(self):
m2 = m1[::-1]
self.assertEqual(m2.hex(), '30' * 200000)

@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: Unexpected keyword argument sep
def test_memoryview_hex_separator(self):
x = bytes(range(97, 102))
m1 = memoryview(x)
Expand Down
16 changes: 6 additions & 10 deletions crates/vm/src/builtins/bytearray.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Implementation of the python bytearray object.
use super::{
PositionIterInternal, PyBytes, PyBytesRef, PyDictRef, PyGenericAlias, PyIntRef, PyStrRef,
PyTuple, PyTupleRef, PyType, PyTypeRef, iter::builtins_iter,
PositionIterInternal, PyBytes, PyDictRef, PyGenericAlias, PyIntRef, PyStrRef, PyTuple,
PyTupleRef, PyType, PyTypeRef, iter::builtins_iter,
};
use crate::{
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
Expand All @@ -10,8 +10,8 @@ use crate::{
atomic_func,
byte::{bytes_from_object, value_from_object},
bytes_inner::{
ByteInnerFindOptions, ByteInnerNewOptions, ByteInnerPaddingOptions, ByteInnerSplitOptions,
ByteInnerTranslateOptions, DecodeArgs, PyBytesInner, bytes_decode,
ByteInnerFindOptions, ByteInnerHexOptions, ByteInnerNewOptions, ByteInnerPaddingOptions,
ByteInnerSplitOptions, ByteInnerTranslateOptions, DecodeArgs, PyBytesInner, bytes_decode,
},
class::PyClassImpl,
common::{
Expand Down Expand Up @@ -321,12 +321,8 @@ impl PyByteArray {
}

#[pymethod]
fn hex(
&self,
sep: OptionalArg<Either<PyStrRef, PyBytesRef>>,
bytes_per_sep: OptionalArg<isize>,
vm: &VirtualMachine,
) -> PyResult<String> {
fn hex(&self, options: ByteInnerHexOptions, vm: &VirtualMachine) -> PyResult<String> {
let ByteInnerHexOptions { sep, bytes_per_sep } = options;
self.inner().hex(sep, bytes_per_sep, vm)
}

Expand Down
8 changes: 4 additions & 4 deletions crates/vm/src/builtins/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use crate::{
anystr::{self, AnyStr},
atomic_func,
bytes_inner::{
ByteInnerFindOptions, ByteInnerNewOptions, ByteInnerPaddingOptions, ByteInnerSplitOptions,
ByteInnerTranslateOptions, DecodeArgs, PyBytesInner, bytes_decode,
ByteInnerFindOptions, ByteInnerHexOptions, ByteInnerNewOptions, ByteInnerPaddingOptions,
ByteInnerSplitOptions, ByteInnerTranslateOptions, DecodeArgs, PyBytesInner, bytes_decode,
},
class::PyClassImpl,
common::{hash::PyHash, lock::PyMutex},
Expand Down Expand Up @@ -318,10 +318,10 @@ impl PyBytes {
#[pymethod]
pub(crate) fn hex(
&self,
sep: OptionalArg<Either<PyStrRef, PyBytesRef>>,
bytes_per_sep: OptionalArg<isize>,
options: ByteInnerHexOptions,
vm: &VirtualMachine,
) -> PyResult<String> {
let ByteInnerHexOptions { sep, bytes_per_sep } = options;
self.inner.hex(sep, bytes_per_sep, vm)
}

Expand Down
12 changes: 4 additions & 8 deletions crates/vm/src/builtins/memory.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use super::{
PositionIterInternal, PyBytes, PyBytesRef, PyGenericAlias, PyInt, PyListRef, PySlice, PyStr,
PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef, PyUtf8StrRef, iter::builtins_iter,
PyTuple, PyTupleRef, PyType, PyTypeRef, PyUtf8StrRef, iter::builtins_iter,
};
use crate::common::lock::LazyLock;
use crate::{
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
TryFromBorrowedObject, TryFromObject, VirtualMachine, atomic_func,
buffer::FormatSpec,
bytes_inner::bytes_to_hex,
bytes_inner::{ByteInnerHexOptions, bytes_to_hex},
class::PyClassImpl,
common::{
borrow::{BorrowedValue, BorrowedValueMut},
Expand Down Expand Up @@ -739,12 +739,8 @@ impl PyMemoryView {
}

#[pymethod]
fn hex(
&self,
sep: OptionalArg<Either<PyStrRef, PyBytesRef>>,
bytes_per_sep: OptionalArg<isize>,
vm: &VirtualMachine,
) -> PyResult<String> {
fn hex(&self, options: ByteInnerHexOptions, vm: &VirtualMachine) -> PyResult<String> {
let ByteInnerHexOptions { sep, bytes_per_sep } = options;
self.try_not_released(vm)?;
self.contiguous_or_collect(|x| bytes_to_hex(x, sep, bytes_per_sep, vm))
}
Expand Down
8 changes: 8 additions & 0 deletions crates/vm/src/bytes_inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1113,6 +1113,14 @@ pub(crate) fn bytes_decode(
.decode_text(zelf, encoding, errors, vm)
}

#[derive(FromArgs)]
pub(crate) struct ByteInnerHexOptions {
#[pyarg(any, optional)]
pub sep: OptionalArg<Either<PyStrRef, PyBytesRef>>,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does sep strictly allow str or bytes? Otherwise, could you please check if ArgStrOrBytesLike fits better here? ArgStrOrBytesLike allows a little bit more than bytes. So it might not fit.

@devyubin devyubin Jul 20, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion! It was definitely worth double-checking.

sep strictly accepts only str or bytes. This means ArgStrOrBytesLike would be too permissive for this case.

Since ArgStrOrBytesLike falls back to ArgBytesLike for non-string inputs, it accepts any object that implements the buffer protocol (like bytearray, memoryview, and array). CPython explicitly rejects these for sep, so I kept Either<PyStrRef, PyBytesRef> to match CPython's strict type checking exactly.

I also unmarked test_memoryview_hex_separator as requested. I verified it locally and the test passed (test_memoryview -> SUCCESS, run=171).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much for confiriming!

#[pyarg(any, optional)]
pub bytes_per_sep: OptionalArg<isize>,
}

fn hex_impl_no_sep(bytes: &[u8]) -> String {
let mut buf: Vec<u8> = vec![0; bytes.len() * 2];
hex::encode_to_slice(bytes, buf.as_mut_slice()).unwrap();
Expand Down
Loading