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: 1 addition & 0 deletions Doc/c-api/concrete.rst
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ Other Objects
descriptor.rst
slice.rst
memoryview.rst
picklebuffer.rst
weakref.rst
capsule.rst
frame.rst
Expand Down
59 changes: 59 additions & 0 deletions Doc/c-api/picklebuffer.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
.. highlight:: c

.. _picklebuffer-objects:

.. index::
pair: object; PickleBuffer

Pickle buffer objects
---------------------

.. versionadded:: 3.8

A :class:`pickle.PickleBuffer` object wraps a :ref:`buffer-providing object
<bufferobjects>` for out-of-band data transfer with the :mod:`pickle` module.


.. c:var:: PyTypeObject PyPickleBuffer_Type

This instance of :c:type:`PyTypeObject` represents the Python pickle buffer type.
This is the same object as :class:`pickle.PickleBuffer` in the Python layer.


.. c:function:: int PyPickleBuffer_Check(PyObject *op)

Return true if *op* is a pickle buffer instance.
This function always succeeds.


.. c:function:: PyObject *PyPickleBuffer_FromObject(PyObject *obj)

Create a pickle buffer from the object *obj*.

This function will fail if *obj* doesn't support the :ref:`buffer protocol <bufferobjects>`.

On success, return a new pickle buffer instance.
On failure, set an exception and return ``NULL``.

Analogous to calling :class:`pickle.PickleBuffer` with *obj* in Python.


.. c:function:: const Py_buffer *PyPickleBuffer_GetBuffer(PyObject *picklebuf)

Get a pointer to the underlying :c:type:`Py_buffer` that the pickle buffer wraps.

The returned pointer is valid as long as *picklebuf* is alive and has not been
released. The caller must not modify or free the returned :c:type:`Py_buffer`.
If the pickle buffer has been released, raise :exc:`ValueError`.

On success, return a pointer to the buffer view.
On failure, set an exception and return ``NULL``.


.. c:function:: int PyPickleBuffer_Release(PyObject *picklebuf)

Release the underlying buffer held by the pickle buffer.

Return ``0`` on success. On failure, set an exception and return ``-1``.

Analogous to calling :meth:`pickle.PickleBuffer.release` in Python.
93 changes: 93 additions & 0 deletions Doc/c-api/structures.rst
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,25 @@ definition with the same method name.
slot. This is helpful because calls to PyCFunctions are optimized more
than wrapper object calls.


.. c:var:: PyTypeObject PyCMethod_Type

The type object corresponding to Python C method objects. This is
available as :class:`types.BuiltinMethodType` in the Python layer.


.. c:function:: int PyCMethod_Check(PyObject *op)

Return true if *op* is an instance of the :c:type:`PyCMethod_Type` type
or a subtype of it. This function always succeeds.


.. c:function:: int PyCMethod_CheckExact(PyObject *op)

This is the same as :c:func:`PyCMethod_Check`, but does not account for
subtypes.


.. c:function:: PyObject * PyCMethod_New(PyMethodDef *ml, PyObject *self, PyObject *module, PyTypeObject *cls)

Turn *ml* into a Python :term:`callable` object.
Expand All @@ -472,6 +491,24 @@ definition with the same method name.
.. versionadded:: 3.9


.. c:var:: PyTypeObject PyCFunction_Type

The type object corresponding to Python C function objects. This is
available as :class:`types.BuiltinFunctionType` in the Python layer.


.. c:function:: int PyCFunction_Check(PyObject *op)

Return true if *op* is an instance of the :c:type:`PyCFunction_Type` type
or a subtype of it. This function always succeeds.


.. c:function:: int PyCFunction_CheckExact(PyObject *op)

This is the same as :c:func:`PyCFunction_Check`, but does not account for
subtypes.


.. c:function:: PyObject * PyCFunction_NewEx(PyMethodDef *ml, PyObject *self, PyObject *module)

Equivalent to ``PyCMethod_New(ml, self, module, NULL)``.
Expand All @@ -482,6 +519,62 @@ definition with the same method name.
Equivalent to ``PyCMethod_New(ml, self, NULL, NULL)``.


.. c:function:: int PyCFunction_GetFlags(PyObject *func)

Get the function's flags on *func* as they were passed to
:c:member:`~PyMethodDef.ml_flags`.

If *func* is not a C function object, this fails with an exception.
*func* must not be ``NULL``.

This function returns the function's flags on success, and ``-1`` with an
exception set on failure.


.. c:function:: int PyCFunction_GET_FLAGS(PyObject *func)

This is the same as :c:func:`PyCFunction_GetFlags`, but without error
or type checking.


.. c:function:: PyCFunction PyCFunction_GetFunction(PyObject *func)

Get the function pointer on *func* as it was passed to
:c:member:`~PyMethodDef.ml_meth`.

If *func* is not a C function object, this fails with an exception.
*func* must not be ``NULL``.

This function returns the function pointer on success, and ``NULL`` with an
exception set on failure.


.. c:function:: int PyCFunction_GET_FUNCTION(PyObject *func)

This is the same as :c:func:`PyCFunction_GetFunction`, but without error
or type checking.


.. c:function:: PyObject *PyCFunction_GetSelf(PyObject *func)

Get the "self" object on *func*. This is the object that would be passed
to the first argument of a :c:type:`PyCFunction`. For C function objects
created through a :c:type:`PyMethodDef` on a :c:type:`PyModuleDef`, this
is the resulting module object.

If *func* is not a C function object, this fails with an exception.
*func* must not be ``NULL``.

This function returns a :term:`borrowed reference` to the "self" object
on success, and ``NULL`` with an exception set on failure.


.. c:function:: PyObject *PyCFunction_GET_SELF(PyObject *func)

This is the same as :c:func:`PyCFunction_GetSelf`, but without error or
type checking.


Accessing attributes of extension types
---------------------------------------

Expand Down
78 changes: 43 additions & 35 deletions Lib/pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -3548,7 +3548,15 @@ def exit_with_permission_help_text():
sys.exit(1)


def main():
def parse_args():
# We want pdb to be as intuitive as possible to users, so we need to do some
# heuristic parsing to deal with ambiguity.
# For example:
# "python -m pdb -m foo -p 1" should pass "-p 1" to "foo".
# "python -m pdb foo.py -m bar" should pass "-m bar" to "foo.py".
# "python -m pdb -m foo -m bar" should pass "-m bar" to "foo".
# This require some customized parsing logic to find the actual debug target.

import argparse

parser = argparse.ArgumentParser(
Expand All @@ -3559,28 +3567,48 @@ def main():
color=True,
)

# We need to maunally get the script from args, because the first positional
# arguments could be either the script we need to debug, or the argument
# to the -m module
# Get all the commands out first. For backwards compatibility, we allow
# -c commands to be after the target.
parser.add_argument('-c', '--command', action='append', default=[], metavar='command', dest='commands',
help='pdb commands to execute as if given in a .pdbrc file')
parser.add_argument('-m', metavar='module', dest='module')
parser.add_argument('-p', '--pid', type=int, help="attach to the specified PID", default=None)

if len(sys.argv) == 1:
opts, args = parser.parse_known_args()

if not args:
# If no arguments were given (python -m pdb), print the whole help message.
# Without this check, argparse would only complain about missing required arguments.
# We need to add the arguments definitions here to get a proper help message.
parser.add_argument('-m', metavar='module', dest='module')
parser.add_argument('-p', '--pid', type=int, help="attach to the specified PID", default=None)
parser.print_help()
sys.exit(2)
elif args[0] == '-p' or args[0] == '--pid':
# Attach to a pid
parser.add_argument('-p', '--pid', type=int, help="attach to the specified PID", default=None)
opts, args = parser.parse_known_args()
if args:
# For --pid, any extra arguments are invalid.
parser.error(f"unrecognized arguments: {' '.join(args)}")
elif args[0] == '-m':
# Debug a module, we only need the first -m module argument.
# The rest is passed to the module itself.
parser.add_argument('-m', metavar='module', dest='module')
opt_module = parser.parse_args(args[:2])
opts.module = opt_module.module
args = args[2:]
elif args[0].startswith('-'):
# Invalid argument before the script name.
invalid_args = list(itertools.takewhile(lambda a: a.startswith('-'), args))
parser.error(f"unrecognized arguments: {' '.join(invalid_args)}")

opts, args = parser.parse_known_args()
# Otherwise it's debugging a script and we already parsed all -c commands.

return opts, args

if opts.pid:
# If attaching to a remote pid, unrecognized arguments are not allowed.
# This will raise an error if there are extra unrecognized arguments.
opts = parser.parse_args()
if opts.module:
parser.error("argument -m: not allowed with argument --pid")
def main():
opts, args = parse_args()

if getattr(opts, 'pid', None) is not None:
try:
attach(opts.pid, opts.commands)
except RuntimeError:
Expand All @@ -3592,30 +3620,10 @@ def main():
except PermissionError:
exit_with_permission_help_text()
return
elif opts.module:
# If a module is being debugged, we consider the arguments after "-m module" to
# be potential arguments to the module itself. We need to parse the arguments
# before "-m" to check if there is any invalid argument.
# e.g. "python -m pdb -m foo --spam" means passing "--spam" to "foo"
# "python -m pdb --spam -m foo" means passing "--spam" to "pdb" and is invalid
idx = sys.argv.index('-m')
args_to_pdb = sys.argv[1:idx]
# This will raise an error if there are invalid arguments
parser.parse_args(args_to_pdb)
else:
# If a script is being debugged, then pdb expects the script name as the first argument.
# Anything before the script is considered an argument to pdb itself, which would
# be invalid because it's not parsed by argparse.
invalid_args = list(itertools.takewhile(lambda a: a.startswith('-'), args))
if invalid_args:
parser.error(f"unrecognized arguments: {' '.join(invalid_args)}")

if opts.module:
elif getattr(opts, 'module', None) is not None:
file = opts.module
target = _ModuleTarget(file)
else:
if not args:
parser.error("no module or script to run")
file = args.pop(0)
if file.endswith('.pyz'):
target = _ZipTarget(file)
Expand Down
5 changes: 4 additions & 1 deletion Lib/test/test_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -3974,7 +3974,10 @@ def test_run_module_with_args(self):
commands = """
continue
"""
self._run_pdb(["calendar", "-m"], commands, expected_returncode=2)
self._run_pdb(["calendar", "-m"], commands, expected_returncode=1)

_, stderr = self._run_pdb(["-m", "calendar", "-p", "1"], commands)
self.assertIn("unrecognized arguments: -p", stderr)

stdout, _ = self._run_pdb(["-m", "calendar", "1"], commands)
self.assertIn("December", stdout)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Refactor the :mod:`pdb` parsing issue so positional arguments can pass through intuitively.
Loading