Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-102500: Implement PEP 688 #102521

Merged
merged 51 commits into from
May 4, 2023
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
ca76251
try 1
JelleZijlstra Oct 2, 2022
901b459
progress
JelleZijlstra Oct 3, 2022
c5407c7
this is better
JelleZijlstra Oct 4, 2022
86f2000
this seems to work
JelleZijlstra Oct 4, 2022
ac10887
one more test
JelleZijlstra Oct 5, 2022
2563016
Merge remote-tracking branch 'upstream/main' into pep688v2
JelleZijlstra Oct 5, 2022
530a160
additions
JelleZijlstra Oct 5, 2022
de3a4bc
Merge remote-tracking branch 'upstream/main' into pep688v2
JelleZijlstra Oct 12, 2022
116f72e
Merge remote-tracking branch 'upstream/main' into pep688v2
JelleZijlstra Oct 21, 2022
207d2fd
introduce __mutable_buffer__ (rather hackily)
JelleZijlstra Oct 21, 2022
aee2c33
Merge remote-tracking branch 'upstream/main' into pep688v2
JelleZijlstra Nov 6, 2022
007fdc1
Rip out __mutable_buffer__
JelleZijlstra Nov 6, 2022
46a9239
__release_buffer__ calls mv.release()
JelleZijlstra Nov 8, 2022
3020fee
Merge remote-tracking branch 'upstream/main' into pep688v2
JelleZijlstra Nov 8, 2022
be9bf45
additional test
JelleZijlstra Nov 8, 2022
a6bf0e8
undo stray change
JelleZijlstra Nov 8, 2022
04d267e
Merge remote-tracking branch 'upstream/main' into pep688v2
JelleZijlstra Dec 18, 2022
04d0a42
throw an error if already released
JelleZijlstra Dec 18, 2022
bb6d076
Merge remote-tracking branch 'upstream/main' into pep688v2
JelleZijlstra Mar 8, 2023
5f755fe
Fix compiler warning
JelleZijlstra Mar 8, 2023
0b02e63
news
JelleZijlstra Mar 8, 2023
8e4db43
fix some tests
JelleZijlstra Mar 8, 2023
6c863bc
More tests. Add flags= argument to memoryview
JelleZijlstra Mar 10, 2023
3b4b7d6
Make memoryview flags arg private
JelleZijlstra Mar 11, 2023
f295012
regen global objects
JelleZijlstra Mar 11, 2023
1660ae4
Merge remote-tracking branch 'upstream/main' into pep688v2
JelleZijlstra Apr 5, 2023
0af94ac
Merge remote-tracking branch 'upstream/main' into pep688v2
JelleZijlstra Apr 5, 2023
b5ea908
Ignore new C globals for now
JelleZijlstra Apr 5, 2023
b22bfa9
not static (should not have committed this)
JelleZijlstra Apr 5, 2023
69e8f7c
Use tabs not spaces
JelleZijlstra Apr 5, 2023
4ca7a7c
Address Kumar's feedback
JelleZijlstra Apr 10, 2023
fd2d716
Merge branch 'main' into pep688v2
JelleZijlstra Apr 10, 2023
a70e12d
Address another piece of feedback
JelleZijlstra Apr 13, 2023
96c9253
Use a classmethod instead of a new arg to the memoryview constructor
JelleZijlstra Apr 13, 2023
1f7f7a0
fix typo
JelleZijlstra Apr 13, 2023
7665dde
Merge branch 'main' into pep688v2
JelleZijlstra Apr 25, 2023
e99c188
Merge branch 'main' into pep688v2
JelleZijlstra Apr 26, 2023
33691ea
Add Py_SAFE_DOWNCAST
JelleZijlstra Apr 26, 2023
8f5073f
Add some test cases (thanks Shantanu)
JelleZijlstra Apr 26, 2023
b69dc7c
Merge remote-tracking branch 'upstream/main' into pep688v2
JelleZijlstra Apr 26, 2023
9d33003
Merge remote-tracking branch 'upstream/main' into pep688v2
JelleZijlstra Apr 29, 2023
cecb6a5
Remove spurious global strings
JelleZijlstra Apr 29, 2023
9b941a0
Merge branch 'main' into pep688v2
JelleZijlstra May 4, 2023
b22d66f
Merge branch 'main' of https://github.com/python/cpython into pep688v2
kumaraditya303 May 4, 2023
0f77bbb
fixup global objects
kumaraditya303 May 4, 2023
61f54ce
minor fixes
kumaraditya303 May 4, 2023
9f2d16b
newlines
JelleZijlstra May 4, 2023
5e0d2de
Rename variable
JelleZijlstra May 4, 2023
b380632
Check for INT_MAX
JelleZijlstra May 4, 2023
8502b98
Remove tp_new
JelleZijlstra May 4, 2023
b51465f
regen-all
JelleZijlstra May 4, 2023
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
3 changes: 3 additions & 0 deletions Include/internal/pycore_global_objects_fini_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(__await__)
STRUCT_FOR_ID(__bases__)
STRUCT_FOR_ID(__bool__)
STRUCT_FOR_ID(__buffer__)
STRUCT_FOR_ID(__build_class__)
STRUCT_FOR_ID(__builtins__)
STRUCT_FOR_ID(__bytes__)
Expand Down Expand Up @@ -180,6 +181,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(__rdivmod__)
STRUCT_FOR_ID(__reduce__)
STRUCT_FOR_ID(__reduce_ex__)
STRUCT_FOR_ID(__release_buffer__)
STRUCT_FOR_ID(__repr__)
STRUCT_FOR_ID(__reversed__)
STRUCT_FOR_ID(__rfloordiv__)
Expand Down Expand Up @@ -235,6 +237,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(_finalizing)
STRUCT_FOR_ID(_find_and_load)
STRUCT_FOR_ID(_fix_up_module)
STRUCT_FOR_ID(_flags)
STRUCT_FOR_ID(_flags_)
STRUCT_FOR_ID(_get_sourcefile)
STRUCT_FOR_ID(_handle_fromlist)
Expand Down
17 changes: 17 additions & 0 deletions Include/internal/pycore_memoryobject.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#ifndef Py_INTERNAL_MEMORYOBJECT_H
#define Py_INTERNAL_MEMORYOBJECT_H
#ifdef __cplusplus
extern "C" {
#endif

#ifndef Py_BUILD_CORE
# error "this header requires Py_BUILD_CORE define"
#endif

PyObject *
PyMemoryView_FromObjectAndFlags(PyObject *v, int flags);

#ifdef __cplusplus
}
#endif
#endif /* !Py_INTERNAL_MEMORYOBJECT_H */
3 changes: 3 additions & 0 deletions Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Include/internal/pycore_typeobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ _Py_type_getattro(PyTypeObject *type, PyObject *name);
PyObject *_Py_slot_tp_getattro(PyObject *self, PyObject *name);
PyObject *_Py_slot_tp_getattr_hook(PyObject *self, PyObject *name);

PyAPI_DATA(PyTypeObject) _PyBufferWrapper_Type;

#ifdef __cplusplus
}
#endif
Expand Down
9 changes: 9 additions & 0 deletions Include/internal/pycore_unicodeobject_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Include/pybuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ PyAPI_FUNC(void) PyBuffer_Release(Py_buffer *view);
/* Maximum number of dimensions */
#define PyBUF_MAX_NDIM 64

/* Flags for getting buffers */
/* Flags for getting buffers. Keep these in sync with inspect.BufferFlags. */
#define PyBUF_SIMPLE 0
#define PyBUF_WRITABLE 0x0001

Expand Down
17 changes: 16 additions & 1 deletion Lib/_collections_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def _f(): pass
"Mapping", "MutableMapping",
"MappingView", "KeysView", "ItemsView", "ValuesView",
"Sequence", "MutableSequence",
"ByteString",
"ByteString", "Buffer",
]

# This module has been renamed from collections.abc to _collections_abc to
Expand Down Expand Up @@ -439,6 +439,21 @@ def __subclasshook__(cls, C):
return NotImplemented


class Buffer(metaclass=ABCMeta):

__slots__ = ()

@abstractmethod
def __buffer__(self, flags: int, /) -> memoryview:
raise NotImplementedError

@classmethod
def __subclasshook__(cls, C):
if cls is Buffer:
return _check_methods(C, "__buffer__")
return NotImplemented


class _CallableGenericAlias(GenericAlias):
""" Represent `Callable[argtypes, resulttype]`.

Expand Down
23 changes: 23 additions & 0 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"Attribute",
"BlockFinder",
"BoundArguments",
"BufferFlags",
"CORO_CLOSED",
"CORO_CREATED",
"CORO_RUNNING",
Expand Down Expand Up @@ -3323,6 +3324,28 @@ def signature(obj, *, follow_wrapped=True, globals=None, locals=None, eval_str=F
globals=globals, locals=locals, eval_str=eval_str)


class BufferFlags(enum.IntFlag):
sobolevn marked this conversation as resolved.
Show resolved Hide resolved
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():
""" Logic for inspecting an object given at command line """
import argparse
Expand Down
125 changes: 125 additions & 0 deletions Lib/test/test_buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import unittest
from test import support
from test.support import os_helper
import inspect
from itertools import permutations, product
from random import randrange, sample, choice
import warnings
Expand Down Expand Up @@ -4436,5 +4437,129 @@ def test_pybuffer_size_from_format(self):
struct.calcsize(format))


class TestPythonBufferProtocol(unittest.TestCase):
def test_basic(self):
class MyBuffer:
def __buffer__(self, flags):
JelleZijlstra marked this conversation as resolved.
Show resolved Hide resolved
return memoryview(b"hello")

mv = memoryview(MyBuffer())
self.assertEqual(mv.tobytes(), b"hello")
self.assertEqual(bytes(MyBuffer()), b"hello")

def test_bad_buffer_method(self):
class MustReturnMV:
JelleZijlstra marked this conversation as resolved.
Show resolved Hide resolved
def __buffer__(self, flags):
return 42

self.assertRaises(TypeError, memoryview, MustReturnMV())

class WrongArity:
def __buffer__(self):
return memoryview(b"hello")

self.assertRaises(TypeError, memoryview, WrongArity())

def test_release_buffer(self):
class WhatToRelease:
def __init__(self):
self.held = False
self.ba = bytearray(b"hello")
def __buffer__(self, flags):
JelleZijlstra marked this conversation as resolved.
Show resolved Hide resolved
if self.held:
raise TypeError("already held")
self.held = True
return memoryview(self.ba)
def __release_buffer__(self, buffer):
self.held = False

wr = WhatToRelease()
self.assertFalse(wr.held)
with memoryview(wr) as mv:
self.assertTrue(wr.held)
self.assertEqual(mv.tobytes(), b"hello")
self.assertFalse(wr.held)

def test_same_buffer_returned(self):
class WhatToRelease:
def __init__(self):
self.held = False
self.ba = bytearray(b"hello")
self.created_mv = None
def __buffer__(self, flags):
if self.held:
raise TypeError("already held")
self.held = True
self.created_mv = memoryview(self.ba)
return self.created_mv
def __release_buffer__(self, buffer):
assert buffer is self.created_mv
self.held = False

wr = WhatToRelease()
self.assertFalse(wr.held)
with memoryview(wr) as mv:
self.assertTrue(wr.held)
self.assertEqual(mv.tobytes(), b"hello")
self.assertFalse(wr.held)

def test_buffer_flags(self):
class PossiblyMutable:
def __init__(self, data, mutable) -> None:
self._data = bytearray(data)
self._mutable = mutable

def __buffer__(self, flags):
if flags & inspect.BufferFlags.WRITABLE:
if not self._mutable:
raise RuntimeError("not mutable")
return memoryview(self._data)
else:
return memoryview(bytes(self._data))

mutable = PossiblyMutable(b"hello", True)
immutable = PossiblyMutable(b"hello", False)
with memoryview(mutable, _flags=inspect.BufferFlags.WRITABLE) as mv:
self.assertEqual(mv.tobytes(), b"hello")
mv[0] = ord(b'x')
self.assertEqual(mv.tobytes(), b"xello")
with memoryview(immutable, _flags=inspect.BufferFlags.SIMPLE) as mv:
self.assertEqual(mv.tobytes(), b"hello")
with self.assertRaises(TypeError):
mv[0] = ord(b'x')
self.assertEqual(mv.tobytes(), b"hello")

with self.assertRaises(RuntimeError):
memoryview(immutable, _flags=inspect.BufferFlags.WRITABLE)
with memoryview(immutable) as mv:
self.assertEqual(mv.tobytes(), b"hello")
with self.assertRaises(TypeError):
mv[0] = ord(b'x')
self.assertEqual(mv.tobytes(), b"hello")

def test_call_builtins(self):
ba = bytearray(b"hello")
mv = ba.__buffer__(0)
self.assertEqual(mv.tobytes(), b"hello")
ba.__release_buffer__(mv)

@unittest.skipIf(_testcapi is None, "requires _testcapi")
def test_c_buffer(self):
buf = _testcapi.testBuf()
self.assertEqual(buf.references, 0)
mv = buf.__buffer__(0)
self.assertIsInstance(mv, memoryview)
self.assertEqual(mv.tobytes(), b"test")
self.assertEqual(buf.references, 1)
buf.__release_buffer__(mv)
self.assertEqual(buf.references, 0)
with self.assertRaises(ValueError):
mv.tobytes()
# Calling it again doesn't cause issues
with self.assertRaises(ValueError):
buf.__release_buffer__(mv)
self.assertEqual(buf.references, 0)


if __name__ == "__main__":
unittest.main()
11 changes: 10 additions & 1 deletion Lib/test/test_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from collections.abc import Set, MutableSet
from collections.abc import Mapping, MutableMapping, KeysView, ItemsView, ValuesView
from collections.abc import Sequence, MutableSequence
from collections.abc import ByteString
from collections.abc import ByteString, Buffer


class TestUserObjects(unittest.TestCase):
Expand Down Expand Up @@ -1949,6 +1949,15 @@ def test_ByteString(self):
self.assertFalse(issubclass(memoryview, ByteString))
self.validate_abstract_methods(ByteString, '__getitem__', '__len__')

def test_Buffer(self):
for sample in [bytes, bytearray, memoryview]:
self.assertIsInstance(sample(b"x"), Buffer)
self.assertTrue(issubclass(sample, Buffer))
for sample in [str, list, tuple]:
self.assertNotIsInstance(sample(), Buffer)
self.assertFalse(issubclass(sample, Buffer))
self.validate_abstract_methods(Buffer, '__buffer__')

def test_MutableSequence(self):
for sample in [tuple, str, bytes]:
self.assertNotIsInstance(sample(), MutableSequence)
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_doctest.py
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,7 @@ def non_Python_modules(): r"""

>>> import builtins
>>> tests = doctest.DocTestFinder().find(builtins)
>>> 830 < len(tests) < 850 # approximate number of objects with docstrings
>>> 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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Make the buffer protocol accessible in Python code using the new
``__buffer__`` and ``__release_buffer__`` magic methods. See :pep:`688` for
details. Patch by Jelle Zijlstra.
2 changes: 1 addition & 1 deletion Modules/Setup.stdlib.in
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/unicode.c _testcapi/getargs.c _testcapi/pytime.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/pyos.c
@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/unicode.c _testcapi/getargs.c _testcapi/pytime.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyos.c
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c

# Some testing modules MUST be built as shared libraries.
Expand Down
Loading