diff --git a/Doc/library/ast.rst b/Doc/library/ast.rst index 0ea183582455cd..ab2a668f590afd 100644 --- a/Doc/library/ast.rst +++ b/Doc/library/ast.rst @@ -806,8 +806,11 @@ Comprehensions List and set comprehensions, generator expressions, and dictionary comprehensions. ``elt`` (or ``key`` and ``value``) is a single node representing the part that will be evaluated for each item. + For dictionary comprehensions using unpacking, for example - ``{**item for item in items}``, ``value`` is ``None``, + ``{**item for item in items}``, the expression to be expanded goes in + ``key`` and ``value`` is ``None``. + ``generators`` is a list of :class:`comprehension` nodes. .. doctest:: diff --git a/Doc/library/email.compat32-message.rst b/Doc/library/email.compat32-message.rst index 5754c2b65b239f..11b4d908fcd7d6 100644 --- a/Doc/library/email.compat32-message.rst +++ b/Doc/library/email.compat32-message.rst @@ -96,7 +96,7 @@ Here are the methods of the :class:`Message` class: text = fp.getvalue() If the message object contains binary data that is not encoded according - to RFC standards, the non-compliant data will be replaced by unicode + to RFC standards, the non-compliant data will be replaced by Unicode "unknown character" code points. (See also :meth:`.as_bytes` and :class:`~email.generator.BytesGenerator`.) diff --git a/Doc/library/email.contentmanager.rst b/Doc/library/email.contentmanager.rst index 04a41667f7dc2c..b28160082f6858 100644 --- a/Doc/library/email.contentmanager.rst +++ b/Doc/library/email.contentmanager.rst @@ -96,9 +96,9 @@ Currently the email package provides only one concrete content manager, This content manager provides only a minimum interface beyond that provided by :class:`~email.message.Message` itself: it deals only with text, raw - byte strings, and :class:`~email.message.Message` objects. Nevertheless, it + bytes, and :class:`~email.message.Message` objects. Nevertheless, it provides significant advantages compared to the base API: ``get_content`` on - a text part will return a unicode string without the application needing to + a text part will return a string without the application needing to manually decode it, ``set_content`` provides a rich set of options for controlling the headers added to a part and controlling the content transfer encoding, and it enables the use of the various ``add_`` methods, thereby @@ -111,7 +111,7 @@ Currently the email package provides only one concrete content manager, parts), or a ``bytes`` object (for all other non-multipart types). Raise a :exc:`KeyError` if called on a ``multipart``. If the part is a ``text`` part and *errors* is specified, use it as the error handler when - decoding the payload to unicode. The default error handler is + decoding the payload to a string. The default error handler is ``replace``. .. method:: set_content(msg, <'str'>, subtype="plain", charset='utf-8', \ diff --git a/Doc/library/email.examples.rst b/Doc/library/email.examples.rst index 492a8354d8bf85..7586cbd25f9f3b 100644 --- a/Doc/library/email.examples.rst +++ b/Doc/library/email.examples.rst @@ -7,7 +7,7 @@ Here are a few examples of how to use the :mod:`email` package to read, write, and send simple email messages, as well as more complex MIME messages. First, let's see how to create and send a simple text message (both the -text content and the addresses may contain unicode characters): +text content and the addresses may contain Unicode characters): .. literalinclude:: ../includes/email-simple.py diff --git a/Doc/library/email.header.rst b/Doc/library/email.header.rst index e7e21d036e07de..65e2d0367fd768 100644 --- a/Doc/library/email.header.rst +++ b/Doc/library/email.header.rst @@ -49,7 +49,7 @@ For example:: Notice here how we wanted the :mailheader:`Subject` field to contain a non-ASCII character? We did this by creating a :class:`Header` instance and passing in -the character set that the byte string was encoded in. When the subsequent +the character set to use when encoding it. When the subsequent :class:`~email.message.Message` instance was flattened, the :mailheader:`Subject` field was properly :rfc:`2047` encoded. MIME-aware mail readers would show this header using the embedded ISO-8859-1 character. @@ -150,7 +150,7 @@ Here is the :class:`Header` class description: .. method:: __str__() Returns an approximation of the :class:`Header` as a string, using an - unlimited line length. All pieces are converted to unicode using the + unlimited line length. All pieces are decoded using the specified encoding and joined together appropriately. Any pieces with a charset of ``'unknown-8bit'`` are decoded as ASCII using the ``'replace'`` error handler. diff --git a/Doc/library/email.headerregistry.rst b/Doc/library/email.headerregistry.rst index 619c17c98e8d89..1cdc0b80c19dc5 100644 --- a/Doc/library/email.headerregistry.rst +++ b/Doc/library/email.headerregistry.rst @@ -40,7 +40,7 @@ headers. *name* and *value* are passed to ``BaseHeader`` from the :attr:`~email.policy.EmailPolicy.header_factory` call. The string value of - any header object is the *value* fully decoded to unicode. + any header object is the *value* fully decoded to a string. This base class defines the following read-only properties: @@ -95,10 +95,10 @@ headers. defects to this list. On return, the ``kwds`` dictionary *must* contain values for at least the keys ``decoded``, ``defects`` and ``parse_tree``. ``decoded`` should be the string value for the header (that is, the header - value fully decoded to unicode). ``parse_tree`` is set to the parse tree obtained + value fully decoded to a string). ``parse_tree`` is set to the parse tree obtained from parsing the header. The parse method should assume that *string* may contain content-transfer-encoded parts, but should correctly handle all valid - unicode characters as well so that it can parse un-encoded header values. + Unicode characters as well so that it can parse un-encoded header values. ``BaseHeader``'s ``__new__`` then creates the header instance, and calls its ``init`` method. The specialized class only needs to provide an ``init`` @@ -126,7 +126,7 @@ headers. mechanism for encoding non-ASCII text as ASCII characters within a header value. When a *value* containing encoded words is passed to the constructor, the ``UnstructuredHeader`` parser converts such encoded words - into unicode, following the :rfc:`2047` rules for unstructured text. The + into a string, following the :rfc:`2047` rules for unstructured text. The parser uses heuristics to attempt to decode certain non-compliant encoded words. Defects are registered in such cases, as well as defects for issues such as invalid characters within the encoded words or the non-encoded text. @@ -203,8 +203,8 @@ headers. the list of addresses is "flattened" into a one dimensional list). The ``decoded`` value of the header will have all encoded words decoded to - unicode. :class:`~encodings.idna` encoded domain names are also decoded to - unicode. The ``decoded`` value is set by :ref:`joining ` the + a string. :class:`~encodings.idna` encoded domain names are also decoded to + a string. The ``decoded`` value is set by :ref:`joining ` the :class:`str` value of the elements of the ``groups`` attribute with ``', '``. @@ -392,7 +392,7 @@ construct structured values to assign to specific headers. *domain*, in which case *username* and *domain* will be parsed from the *addr_spec*. An *addr_spec* must be a properly RFC quoted string; if it is not ``Address`` will raise an error. Unicode characters are allowed and - will be property encoded when serialized. However, per the RFCs, unicode is + will be property encoded when serialized. However, per the RFCs, Unicode is *not* allowed in the username portion of the address. .. attribute:: display_name diff --git a/Doc/library/email.policy.rst b/Doc/library/email.policy.rst index 816d02d86f4fc4..82a2b305f8daed 100644 --- a/Doc/library/email.policy.rst +++ b/Doc/library/email.policy.rst @@ -505,7 +505,7 @@ added matters. To illustrate:: Otherwise the *name*, and the *value* with any CR or LF characters removed, are passed to the ``header_factory``, and the resulting header object is returned. Any surrogateescaped bytes get turned into - the unicode unknown-character glyph. + the Unicode unknown-character glyph. .. method:: fold(name, value) @@ -600,10 +600,10 @@ the email package is changed from the Python 3.2 API in the following ways: From the application view, this means that any header obtained through the :class:`~email.message.EmailMessage` is a header object with extra -attributes, whose string value is the fully decoded unicode value of the +attributes, whose string value is the fully decoded value of the header. Likewise, a header may be assigned a new value, or a new header -created, using a unicode string, and the policy will take care of converting -the unicode string into the correct RFC encoded form. +created, using a string, and the policy will take care of converting +the string into the correct RFC encoded form. The header objects and their attributes are described in :mod:`~email.headerregistry`. diff --git a/Doc/library/email.rst b/Doc/library/email.rst index 98b47ffd74096c..d1a3d9a63eca2e 100644 --- a/Doc/library/email.rst +++ b/Doc/library/email.rst @@ -54,7 +54,7 @@ server. The email package does its best to hide the details of the various governing RFCs from the application. Conceptually the application should be able to -treat the email message as a structured tree of unicode text and binary +treat the email message as a structured tree of Unicode text and binary attachments, without having to worry about how these are represented when serialized. In practice, however, it is often necessary to be aware of at least some of the rules governing MIME messages and their structure, @@ -84,7 +84,7 @@ to advanced applications. Following those is a set of examples of using the fundamental parts of the APIs covered in the preceding sections. -The foregoing represent the modern (unicode friendly) API of the email package. +The foregoing represent the modern (Unicode friendly) API of the email package. The remaining sections, starting with the :class:`~email.message.Message` class, cover the legacy :data:`~email.policy.compat32` API that deals much more directly with the details of how email messages are represented. The diff --git a/Doc/library/email.utils.rst b/Doc/library/email.utils.rst index 0a96dbfd196a05..acf7093ae10a80 100644 --- a/Doc/library/email.utils.rst +++ b/Doc/library/email.utils.rst @@ -212,7 +212,7 @@ of the new API. When a header parameter is encoded in :rfc:`2231` format, :meth:`Message.get_param ` may return a 3-tuple containing the character set, - language, and value. :func:`collapse_rfc2231_value` turns this into a unicode + language, and value. :func:`collapse_rfc2231_value` turns this into a string. Optional *errors* is passed to the *errors* argument of :class:`str`'s :func:`~str.encode` method; it defaults to ``'replace'``. Optional *fallback_charset* specifies the character set to use if the one in the diff --git a/Doc/library/unittest.mock.rst b/Doc/library/unittest.mock.rst index cce8f2833ac5a0..b9e6f65cb917df 100644 --- a/Doc/library/unittest.mock.rst +++ b/Doc/library/unittest.mock.rst @@ -231,9 +231,10 @@ the *new_callable* argument to :func:`patch`. Create a new :class:`Mock` object. :class:`Mock` takes several optional arguments that specify the behaviour of the Mock object: - * *spec*: This can be either a list of strings or an existing object (a - class or instance) that acts as the specification for the mock object. If - you pass in an object then a list of strings is formed by calling dir on + * *spec*: This can be either a list or tuple of strings, + or an existing object (a class or instance) + that acts as the specification for the mock object. + If you pass in an object then a list of strings is formed by calling dir on the object (excluding unsupported magic attributes and methods). Accessing any attribute not in this list will raise an :exc:`AttributeError`. @@ -241,6 +242,9 @@ the *new_callable* argument to :func:`patch`. :attr:`~object.__class__` returns the class of the spec object. This allows mocks to pass :func:`isinstance` tests. + .. versionchanged:: next + :func:`dir` now works for a mock created with a tuple *spec*. + * *spec_set*: A stricter variant of *spec*. If used, attempting to *set* or get an attribute on the mock that isn't on the object passed as *spec_set* will raise an :exc:`AttributeError`. @@ -448,9 +452,9 @@ the *new_callable* argument to :func:`patch`. .. method:: mock_add_spec(spec, spec_set=False) - Add a spec to a mock. *spec* can either be an object or a - list of strings. Only attributes on the *spec* can be fetched as - attributes from the mock. + Add a spec to a mock. + *spec* can either be an object or a list or tuple of strings. + Only attributes on the *spec* can be fetched as attributes from the mock. If *spec_set* is true then only attributes on the spec can be set. diff --git a/Include/object.h b/Include/object.h index 20c2dab4401fef..549b91ebecf4f2 100644 --- a/Include/object.h +++ b/Include/object.h @@ -490,7 +490,7 @@ given type object has a specified feature. #define Py_TPFLAGS_SEQUENCE (1 << 5) /* Set if instances of the type object are treated as mappings for pattern matching */ #define Py_TPFLAGS_MAPPING (1 << 6) -#endif +#endif // Py_LIMITED_API /* Disallow creating instances of the type: set tp_new to NULL and don't create * the "__new__" key in the type dictionary. */ diff --git a/Lib/email/_encoded_words.py b/Lib/email/_encoded_words.py index 05a34a4c105233..ecbaecb7a68ed5 100644 --- a/Lib/email/_encoded_words.py +++ b/Lib/email/_encoded_words.py @@ -161,10 +161,10 @@ def decode(ew): This function expects exactly such a string (that is, it does not check the syntax and may raise errors if the string is not well formed), and returns the encoded_string decoded first from its Content Transfer Encoding and - then from the resulting bytes into unicode using the specified charset. If - the cte-decoded string does not successfully decode using the specified + then from the resulting bytes into a string using the specified charset. + If the cte-decoded string does not successfully decode using the specified character set, a defect is added to the defects list and the unknown octets - are replaced by the unicode 'unknown' character \\uFDFF. + are replaced by the Unicode 'unknown' character \\uFDFF. The specified charset and language are returned. The default for language, which is rarely if ever encountered, is the empty string. @@ -176,7 +176,7 @@ def decode(ew): # Recover the original bytes and do CTE decoding. bstring = cte_string.encode('ascii', 'surrogateescape') bstring, defects = _cte_decoders[cte](bstring) - # Turn the CTE decoded bytes into unicode. + # Turn the CTE decoded bytes into a string. try: string = bstring.decode(charset) except UnicodeDecodeError: diff --git a/Lib/email/_policybase.py b/Lib/email/_policybase.py index e23843df44881f..ab40be6b9204f4 100644 --- a/Lib/email/_policybase.py +++ b/Lib/email/_policybase.py @@ -365,7 +365,7 @@ def _fold(self, name, value, sanitize): charset=_charset.UNKNOWN8BIT, header_name=name) else: - # If we have raw 8bit data in a byte string, we have no idea + # If we have raw 8bit data in a string, we have no idea # what the encoding is. There is no safe way to split this # string. If it's ascii-subset, then we could do a normal # ascii split, but if it's multibyte then we could break the diff --git a/Lib/email/charset.py b/Lib/email/charset.py index 5981791820e740..44fe0794bb73f3 100644 --- a/Lib/email/charset.py +++ b/Lib/email/charset.py @@ -177,8 +177,8 @@ def add_codec(charset, codecname): """Add a codec that map characters in the given charset to/from Unicode. charset is the canonical name of a character set. codecname is the name - of a Python codec, as appropriate for the second argument to the unicode() - built-in, or to the encode() method of a Unicode string. + of a Python codec, as appropriate for the second argument to the str() + built-in, or to the encode() method of a string. """ CODEC_MAP[charset] = codecname @@ -238,8 +238,8 @@ class Charset: """ def __init__(self, input_charset=DEFAULT_CHARSET): # RFC 2046, $4.1.2 says charsets are not case sensitive. We coerce to - # unicode because its .lower() is locale insensitive. If the argument - # is already a unicode, we leave it at that, but ensure that the + # str because its .lower() is locale insensitive. If the argument + # is already a str, we leave it at that, but ensure that the # charset is ASCII, as the standard (RFC XXX) requires. try: if isinstance(input_charset, str): @@ -365,7 +365,7 @@ def header_encode_lines(self, string, maxlengths): # encoded word must stand on its own. So the problem is you have to # encode to bytes to figure out this word's length, but you must split # on characters. This causes two problems: first, we don't know how - # many octets a specific substring of unicode characters will get + # many octets a specific substring of characters will get # encoded to, and second, we don't know how many ASCII characters # those octets will get encoded to. Unless we try it. Which seems # inefficient. In the interest of being correct rather than fast (and diff --git a/Lib/email/header.py b/Lib/email/header.py index 220a84a7454b21..ea1f84ea964ca6 100644 --- a/Lib/email/header.py +++ b/Lib/email/header.py @@ -189,7 +189,7 @@ def __init__(self, s=None, charset=None, Optional s is the initial header value. If None, the initial header value is not set. You can later append to the header with .append() - method calls. s may be a byte string or a Unicode string, but see the + method calls. s may be a byte string or a string, but see the .append() documentation for semantics. Optional charset serves two purposes: it has the same meaning as the @@ -263,7 +263,7 @@ def __str__(self): # have or explicitly disable <, <=, >, >= operators? def __eq__(self, other): # other may be a Header or a string. Both are fine so coerce - # ourselves to a unicode (of the unencoded header value), swap the + # ourselves to a str (of the unencoded header value), swap the # args and do another comparison. return other == str(self) @@ -275,10 +275,10 @@ def append(self, s, charset=None, errors='strict'): value of None (the default) means that the charset given in the constructor is used. - s may be a byte string or a Unicode string. If it is a byte string + s may be a byte string or a string. If it is a byte string (i.e. isinstance(s, str) is false), then charset is the encoding of that byte string, and a UnicodeError will be raised if the string - cannot be decoded with that charset. If s is a Unicode string, then + cannot be decoded with that charset. If s is a string, then charset is a hint specifying the character set of the characters in the string. In either case, when producing an RFC 2822 compliant header using RFC 2047 rules, the string will be encoded using the @@ -397,7 +397,7 @@ def encode(self, splitchars=';, \t', maxlinelen=None, linesep='\n'): def _normalize(self): # Step 1: Normalize the chunks so that all runs of identical charsets - # get collapsed into a single unicode string. + # get collapsed into a single string. chunks = [] last_charset = None last_chunk = [] diff --git a/Lib/email/message.py b/Lib/email/message.py index 641fb2e944d431..64e6c1e23cfb47 100644 --- a/Lib/email/message.py +++ b/Lib/email/message.py @@ -98,7 +98,7 @@ def _parseparam(s): def _unquotevalue(value): # This is different than utils.collapse_rfc2231_value() because it doesn't - # try to convert the value to a unicode. Message.get_param() and + # try to convert the value to a str. Message.get_param() and # Message.get_params() are both currently defined to return the tuple in # the face of RFC 2231 parameters. if isinstance(value, tuple): @@ -182,7 +182,7 @@ def as_string(self, unixfrom=False, maxheaderlen=0, policy=None): If the message object contains binary data that is not encoded according to RFC standards, the non-compliant data will be replaced by - unicode "unknown character" code points. + Unicode "unknown character" code points. """ from email.generator import Generator policy = self.policy if policy is None else policy @@ -315,7 +315,7 @@ def get_payload(self, i=None, decode=False): bpayload = payload.encode('ascii', 'surrogateescape') except UnicodeEncodeError: # This won't happen for RFC compliant messages (messages - # containing only ASCII code points in the unicode input). + # containing only ASCII code points in the string input). # If it does happen, turn the string into bytes in a way # guaranteed not to fail. bpayload = payload.encode('raw-unicode-escape') @@ -395,7 +395,7 @@ def set_charset(self, charset): try: cte(self) except TypeError: - # This 'if' is for backward compatibility, it allows unicode + # This 'if' is for backward compatibility, it allows str # through even though that won't work correctly if the # message is serialized. payload = self._payload diff --git a/Lib/email/policy.py b/Lib/email/policy.py index 4169150101a29d..d09d2f5238d40b 100644 --- a/Lib/email/policy.py +++ b/Lib/email/policy.py @@ -160,7 +160,7 @@ def header_fetch_parse(self, name, value): Otherwise the name and the value with any linesep characters removed are passed to the header_factory method, and the resulting custom header object is returned. Any surrogateescaped bytes get turned - into the unicode unknown-character glyph. + into the Unicode unknown-character glyph. """ if hasattr(value, 'name'): @@ -201,7 +201,7 @@ def fold_binary(self, name, value): data consists of single byte characters or multibyte characters. If utf8 is true, headers are encoded to utf8, otherwise to ascii with - non-ASCII unicode rendered as encoded words. + non-ASCII characters rendered as encoded words. """ folded = self._fold(name, value, refold_binary=self.cte_type=='7bit') diff --git a/Lib/email/utils.py b/Lib/email/utils.py index 6889c5591bf030..c8a6d0af37532c 100644 --- a/Lib/email/utils.py +++ b/Lib/email/utils.py @@ -58,7 +58,7 @@ def _has_surrogates(s): # How to deal with a string containing bytes before handing it to the # application through the 'normal' interface. def _sanitize(string): - # Turn any escaped bytes into unicode 'unknown' char. If the escaped + # Turn any escaped bytes into the Unicode 'unknown' char. If the escaped # bytes happen to be utf-8 they will instead get decoded, even if they # were invalid in the charset the source was supposed to be in. This # seems like it is not a bad thing; a defect was still registered. @@ -460,7 +460,7 @@ def collapse_rfc2231_value(value, errors='replace', fallback_charset='us-ascii'): if not isinstance(value, tuple) or len(value) != 3: return unquote(value) - # While value comes to us as a unicode string, we need it to be a bytes + # While value comes to us as a string, we need it to be a bytes # object. We do not want bytes() normal utf-8 decoder, we want a straight # interpretation of the string as character bytes. charset, language, text = value diff --git a/Lib/test/test_capi/test_type.py b/Lib/test/test_capi/test_type.py index e6a8ef9eed6fc4..90b1f57d1c3c9d 100644 --- a/Lib/test/test_capi/test_type.py +++ b/Lib/test/test_capi/test_type.py @@ -2,6 +2,9 @@ import unittest _testcapi = import_helper.import_module('_testcapi') +_testlimitedcapi = import_helper.import_module('_testlimitedcapi') + +NULL = None class BuiltinStaticTypesTests(unittest.TestCase): @@ -39,15 +42,18 @@ def test_tp_mro_is_set(self): class TypeTests(unittest.TestCase): def test_get_type_name(self): + # Test PyType_GetName(), PyType_GetQualName(), + # PyType_GetFullyQualifiedName() and PyType_GetModuleName(). + class MyType: pass - from _testcapi import ( + from _testlimitedcapi import ( get_type_name, get_type_qualname, get_type_fullyqualname, get_type_module_name) from collections import OrderedDict - ht = _testcapi.get_heaptype_for_name() + ht = _testlimitedcapi.get_heaptype_for_name() for cls, fullname, modname, qualname, name in ( (int, 'int', @@ -107,6 +113,15 @@ class MyType: MyType.__module__ = 123 self.assertEqual(get_type_fullyqualname(MyType), 'my_qualname') + # CRASHES get_type_name(NULL) + # CRASHES get_type_qualname(NULL) + # CRASHES get_type_fullyqualname(NULL) + # CRASHES get_type_module_name(NULL) + # CRASHES get_type_name(object()): argument must be a type + # CRASHES get_type_qualname(object()): argument must be a type + # CRASHES get_type_fullyqualname(object()): argument must be a type + # CRASHES get_type_module_name(object()): argument must be a type + def test_get_base_by_token(self): def get_base_by_token(src, key, comparable=True): def run(use_mro): @@ -215,7 +230,7 @@ class H2(int): pass def test_freeze(self): # test PyType_Freeze() - type_freeze = _testcapi.type_freeze + type_freeze = _testlimitedcapi.type_freeze # simple case, no inherante class MyType: @@ -245,12 +260,15 @@ class D(A, C): pass # as well type_freeze(D) + # CRASHES type_freeze(NULL) + # CRASHES type_freeze(object()): argument must be a type + @unittest.skipIf( Py_GIL_DISABLED and refleak_helper.hunting_for_refleaks(), "Specialization failure triggers gh-127773") def test_freeze_meta(self): """test PyType_Freeze() with overridden MRO""" - type_freeze = _testcapi.type_freeze + type_freeze = _testlimitedcapi.type_freeze class Base: value = 1 @@ -299,3 +317,181 @@ def test_extension_managed_weakref_nogc_type(self): "flag but not Py_TPFLAGS_HAVE_GC flag") with self.assertRaisesRegex(SystemError, msg): _testcapi.create_managed_weakref_nogc_type() + + def test_type_ready(self): + # Test PyType_Ready(): calling it on initialized types + # must not raise an exception. + type_ready = _testlimitedcapi.type_ready + + class HeapType: + pass + + type_ready(int) + type_ready(dict) + type_ready(HeapType) + + # CRASHES type_ready(NULL) + # CRASHES type_ready(123): argument must be a type + + def test_type_clearcache(self): + # Test PyType_ClearCache() + type_clearcache = _testlimitedcapi.type_clearcache + version_tag = type_clearcache() + self.assertEqual(type(version_tag), int) + self.assertGreaterEqual(version_tag, 0) + + def test_type_getflags(self): + # Test PyType_GetFlags() + type_getflags = _testlimitedcapi.type_getflags + + from _testlimitedcapi import ( + Py_TPFLAGS_HEAPTYPE, + Py_TPFLAGS_HAVE_GC, + Py_TPFLAGS_HAVE_FINALIZE, + Py_TPFLAGS_HAVE_VERSION_TAG, + Py_TPFLAGS_VALID_VERSION_TAG, + Py_TPFLAGS_HAVE_VECTORCALL, + Py_TPFLAGS_DISALLOW_INSTANTIATION, + Py_TPFLAGS_IMMUTABLETYPE, + Py_TPFLAGS_READY, + Py_TPFLAGS_READYING, + Py_TPFLAGS_LONG_SUBCLASS, + Py_TPFLAGS_LIST_SUBCLASS, + Py_TPFLAGS_TUPLE_SUBCLASS, + Py_TPFLAGS_BYTES_SUBCLASS, + Py_TPFLAGS_UNICODE_SUBCLASS, + Py_TPFLAGS_DICT_SUBCLASS, + Py_TPFLAGS_BASE_EXC_SUBCLASS, + Py_TPFLAGS_TYPE_SUBCLASS, + Py_TPFLAGS_IS_ABSTRACT, + Py_TPFLAGS_BASETYPE, + _Py_TPFLAGS_MATCH_SELF, + Py_TPFLAGS_ITEMS_AT_END, + Py_TPFLAGS_METHOD_DESCRIPTOR, + ) + from _testcapi import ( + _Py_TPFLAGS_STATIC_BUILTIN, + Py_TPFLAGS_SEQUENCE, + Py_TPFLAGS_MAPPING, + Py_TPFLAGS_INLINE_VALUES, + Py_TPFLAGS_MANAGED_WEAKREF, + Py_TPFLAGS_MANAGED_DICT, + ) + + def check_flag(flags, flag, expected): + self.assertEqual(bool(flags & flag), expected) + + def check_subclasses(test_type, flags): + for flag, base_type in ( + (Py_TPFLAGS_LONG_SUBCLASS, int), + (Py_TPFLAGS_LIST_SUBCLASS, list), + (Py_TPFLAGS_TUPLE_SUBCLASS, tuple), + (Py_TPFLAGS_BYTES_SUBCLASS, bytes), + (Py_TPFLAGS_UNICODE_SUBCLASS, str), + (Py_TPFLAGS_DICT_SUBCLASS, dict), + (Py_TPFLAGS_BASE_EXC_SUBCLASS, BaseException), + (Py_TPFLAGS_TYPE_SUBCLASS, type), + ): + with self.subTest(test_type=test_type, flag=flag, base_type=base_type): + check_flag(flags, flag, issubclass(test_type, base_type)) + + def check_type(test_type, static_type, have_gc=False, have_vectorcall=False, + is_base_type=True, sequence=False, mapping=False, + match_self=True, items_at_end=False): + heap_type = not static_type + + flags = type_getflags(test_type) + check_flag(flags, _Py_TPFLAGS_STATIC_BUILTIN, static_type) + check_flag(flags, Py_TPFLAGS_HEAPTYPE, heap_type) + check_flag(flags, Py_TPFLAGS_HAVE_GC, have_gc) + check_subclasses(test_type, flags) + check_flag(flags, Py_TPFLAGS_HAVE_VECTORCALL, have_vectorcall) + check_flag(flags, Py_TPFLAGS_DISALLOW_INSTANTIATION, False) + check_flag(flags, Py_TPFLAGS_IMMUTABLETYPE, static_type) + check_flag(flags, Py_TPFLAGS_READY, True) + check_flag(flags, Py_TPFLAGS_READYING, False) + check_flag(flags, Py_TPFLAGS_IS_ABSTRACT, False) + check_flag(flags, Py_TPFLAGS_BASETYPE, is_base_type) + check_flag(flags, Py_TPFLAGS_SEQUENCE, sequence) + check_flag(flags, Py_TPFLAGS_MAPPING, mapping) + + check_flag(flags, Py_TPFLAGS_INLINE_VALUES, heap_type) + check_flag(flags, Py_TPFLAGS_MANAGED_WEAKREF, heap_type) + check_flag(flags, Py_TPFLAGS_MANAGED_DICT, heap_type) + check_flag(flags, Py_TPFLAGS_ITEMS_AT_END, items_at_end) + check_flag(flags, Py_TPFLAGS_METHOD_DESCRIPTOR, False) + + check_flag(flags, _Py_TPFLAGS_MATCH_SELF, match_self) + + # Flags kept for backward compatibility + check_flag(flags, Py_TPFLAGS_HAVE_FINALIZE, False) + check_flag(flags, Py_TPFLAGS_HAVE_VERSION_TAG, False) + check_flag(flags, Py_TPFLAGS_VALID_VERSION_TAG, False) + + # Scalar types + check_type(int, static_type=True) + check_type(bool, static_type=True, + is_base_type=False) + check_type(float, static_type=True) + check_type(complex, static_type=True, + match_self=False) + check_type(bytes, static_type=True) + check_type(bytearray, static_type=True) + check_type(str, static_type=True) + + # Collection types + check_type(tuple, static_type=True, have_gc=True, + sequence=True) + check_type(list, static_type=True, have_gc=True, + sequence=True) + check_type(dict, static_type=True, have_gc=True, + mapping=True) + check_type(frozendict, static_type=True, have_gc=True, + mapping=True) + check_type(set, static_type=True, have_gc=True) + check_type(frozenset, static_type=True, have_gc=True) + + # Other types + check_type(BaseException, static_type=True, have_gc=True, + match_self=False) + check_type(type, static_type=True, have_gc=True, + have_vectorcall=True, + match_self=False, + items_at_end=True) + + # Heap type + class HeapType: + pass + check_type(HeapType, static_type=False, have_gc=True, match_self=False) + + # CRASHES type_getflags(NULL) + + def test_type_issubtype(self): + # Test PyType_IsSubtype() + _type_issubtype = _testlimitedcapi.type_issubtype + + def type_issubtype(type1, type2): + res = _type_issubtype(type1, type2) + self.assertIn(res, (0, 1)) + return bool(res) + + class MyList(list): + pass + + self.assertTrue(type_issubtype(bool, int)) + self.assertTrue(type_issubtype(MyList, list)) + + self.assertFalse(type_issubtype(int, type)) + self.assertFalse(type_issubtype(frozendict, dict)) + self.assertFalse(type_issubtype(MyList, tuple)) + + def test_type_modified(self): + # Test PyType_Modified() + type_modified = _testlimitedcapi.type_modified + + class MyType: + pass + type_modified(MyType) + + # CRASHES type_modified(NULL) + # CRASHES type_modified({}): argument must be a type diff --git a/Lib/test/test_type_cache.py b/Lib/test/test_type_cache.py index 9827f2498554a5..0031e4b59c67c9 100644 --- a/Lib/test/test_type_cache.py +++ b/Lib/test/test_type_cache.py @@ -11,13 +11,15 @@ except ImportError: _clear_type_cache = None -# Skip this test if the _testcapi module isn't available. +# Skip this test if the _testcapi modules are not available. _testcapi = import_helper.import_module("_testcapi") +_testlimitedcapi = import_helper.import_module("_testlimitedcapi") _testinternalcapi = import_helper.import_module("_testinternalcapi") + type_get_version = _testcapi.type_get_version type_assign_specific_version_unsafe = _testinternalcapi.type_assign_specific_version_unsafe type_assign_version = _testcapi.type_assign_version -type_modified = _testcapi.type_modified +type_modified = _testlimitedcapi.type_modified def clear_type_cache(): with warnings.catch_warnings(): diff --git a/Lib/test/test_unittest/testmock/testmock.py b/Lib/test/test_unittest/testmock/testmock.py index 764585ec5d5468..ffc89dc5ce1562 100644 --- a/Lib/test/test_unittest/testmock/testmock.py +++ b/Lib/test/test_unittest/testmock/testmock.py @@ -1073,6 +1073,10 @@ def test_dir(self): mock.__iter__ = lambda s: iter([]) self.assertIn('__iter__', dir(mock)) + # spec from a tuple + mock_tuple_spec = Mock(spec=('something',)) + self.assertIn('something', dir(mock_tuple_spec)) + def test_dir_from_spec(self): mock = Mock(spec=unittest.TestCase) diff --git a/Lib/test/test_wsgiref.py b/Lib/test/test_wsgiref.py index 32ef0ccf4e638d..484a2db1637861 100644 --- a/Lib/test/test_wsgiref.py +++ b/Lib/test/test_wsgiref.py @@ -66,6 +66,26 @@ def header_app(environ, start_response): ]).encode('iso-8859-1')] +def input_app(func_name, *args): + def app(e,s): + req = getattr(e['wsgi.input'], func_name)(*args) + s("200 OK", [("Content-Type", "text/plain; charset=utf-8")]) + if type(req) is list: + resp = b";".join(req) + else: + resp = req + return [resp] + return app + + +def errors_app(func_name, *args): + def app(e,s): + getattr(e['wsgi.errors'], func_name)(*args) + s("200 OK", [("Content-Type", "text/plain; charset=utf-8")]) + return [b"data"] + return app + + def run_amock(app=hello_app, data=b"GET / HTTP/1.0\n\n"): server = make_server("", 80, app, MockServer, MockHandler) inp = BufferedReader(BytesIO(data)) @@ -192,6 +212,95 @@ def bad_app(e,s): err.splitlines()[-2], "AssertionError" ) + def test_wsgi_input_read(self): + bad_app = input_app("read") + good_app = input_app("read", 5) + + out, err = run_amock(validator(bad_app)) + self.assertEndsWith(out, + b"A server error occurred. Please contact the administrator." + ) + + self.assertEqual( + err.splitlines()[-2], "AssertionError" + ) + + out, err = run_amock(validator(good_app), b"GET / HTTP/1.0\n\nTest 1\nTest 2\n") + self.assertEndsWith(out, b"Test ") + + def test_wsgi_input_readlines(self): + bad_app = input_app("readlines", 3, 5) + good_app = input_app("readlines", 1) + + out, err = run_amock(validator(bad_app)) + self.assertEndsWith(out, + b"A server error occurred. Please contact the administrator." + ) + self.assertEqual( + err.splitlines()[-2], "AssertionError" + ) + out, err = run_amock(validator(good_app), b"GET / HTTP/1.0\n\nTest Line 1\nTest Line 2\n") + self.assertEndsWith(out, b"Test Line 1\n") + + def test_wsgi_input_readline(self): + bad_app = input_app("readline", 3, 4) + good_app = input_app("readline", 2) + + out, err = run_amock(validator(bad_app)) + self.assertEndsWith(out, + b"A server error occurred. Please contact the administrator." + ) + self.assertEqual( + err.splitlines()[-2], "AssertionError" + ) + + out, err = run_amock(validator(good_app), b"GET / HTTP/1.0\n\nTest 1\nTest 2\n") + self.assertEndsWith(out, b"Te") + + def test_wsgi_input_close(self): + app = input_app("close") + out, err = run_amock(validator(app), b"GET / HTTP/1.0\n\nTest 1\nTest 2\n") + self.assertEqual(err.splitlines()[-2], 'AssertionError: input.close() must not be called') + self.assertEndsWith(out, b"A server error occurred. Please contact the administrator.") + + def test_wsgi_input_iter(self): + def app(e,s): + req = [] + for line in e['wsgi.input']: + req.append(line) + s("200 OK", [('Content-Type', 'text/plain; charser=utf-8')]) + return [b';'.join(req)] + + out, err = run_amock(validator(app), b"GET / HTTP/1.0\n\nTest 1\nTest 2\n") + self.assertEndsWith(out, b"Test 1\n;Test 2\n") + + def test_wsgi_errors_write(self): + bad_app = errors_app("write", b"Test") + good_app = errors_app("write", "Test") + + out, err = run_amock(validator(bad_app), b"GET / HTTP/1.0\n\n") + self.assertEqual(err.splitlines()[-2], 'AssertionError') + + out, err = run_amock(validator(good_app), b"GET / HTTP/1.0\n\n") + self.assertStartsWith(err, "Test") + + def test_wsgi_errors_writelines(self): + bad_app = errors_app("writelines", [1, "Test"]) + good_app = errors_app("writelines", ["Test", "Test"]) + + out, err = run_amock(validator(bad_app), b"GET / HTTP/1.0\n\n") + self.assertEqual(err.splitlines()[-2], 'AssertionError') + + out, err = run_amock(validator(good_app), b"GET / HTTP/1.0\n\n") + self.assertStartsWith(err, "TestTest") + + def test_wsgi_errors_close(self): + app = errors_app("close") + + out, err = run_amock(validator(app), b"GET / HTTP/1.0\n\n") + self.assertEqual(err.splitlines()[-2], + 'AssertionError: errors.close() must not be called') + @force_not_colorized def test_bytes_validation(self): def app(e, s): diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 49011faaa51eae..d0ca94668e05bc 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -798,7 +798,7 @@ def __dir__(self): from_type = [e for e in from_type if not e.startswith('_')] from_dict = [e for e in from_dict if not e.startswith('_') or _is_magic(e)] - return sorted(set(extras + from_type + from_dict + from_child_mocks)) + return sorted({*extras, *from_type, *from_dict, *from_child_mocks}) def __setattr__(self, name, value): diff --git a/Misc/NEWS.d/next/Library/2026-08-12-14-30-00.gh-issue-80762.Kp3vQa.rst b/Misc/NEWS.d/next/Library/2026-08-12-14-30-00.gh-issue-80762.Kp3vQa.rst new file mode 100644 index 00000000000000..080fee4855a5e2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-12-14-30-00.gh-issue-80762.Kp3vQa.rst @@ -0,0 +1,3 @@ +Fix :func:`dir` on :class:`unittest.mock.Mock` objects +created with a tuple *spec*: it raised :exc:`TypeError`. +Such *spec* is now also documented. diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index eaf8777a56c59b..440d5a71608ba9 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -174,7 +174,7 @@ @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tuple.c _testinternalcapi/typecache.c @MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c -@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/capsule.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c _testlimitedcapi/run.c +@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/capsule.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c _testlimitedcapi/run.c _testlimitedcapi/type.c @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c diff --git a/Modules/_testcapi/type.c b/Modules/_testcapi/type.c index f566efa0ca15ae..91203c7bf0f678 100644 --- a/Modules/_testcapi/type.c +++ b/Modules/_testcapi/type.c @@ -1,3 +1,7 @@ +// Thin wrappers to PyType functions. +// Do no check PyType_Check() so Python tests can pass arbitrary objects, +// even if it's likely to crash. + #include "parts.h" #include "util.h" @@ -13,50 +17,11 @@ static PyType_Spec HeapTypeNameType_Spec = { .slots = HeapTypeNameType_slots, }; -static PyObject * -get_heaptype_for_name(PyObject *self, PyObject *Py_UNUSED(ignored)) -{ - return PyType_FromSpec(&HeapTypeNameType_Spec); -} - - -static PyObject * -get_type_name(PyObject *self, PyObject *type) -{ - assert(PyType_Check(type)); - return PyType_GetName((PyTypeObject *)type); -} - - -static PyObject * -get_type_qualname(PyObject *self, PyObject *type) -{ - assert(PyType_Check(type)); - return PyType_GetQualName((PyTypeObject *)type); -} - - -static PyObject * -get_type_fullyqualname(PyObject *self, PyObject *type) -{ - assert(PyType_Check(type)); - return PyType_GetFullyQualifiedName((PyTypeObject *)type); -} - - -static PyObject * -get_type_module_name(PyObject *self, PyObject *type) -{ - assert(PyType_Check(type)); - return PyType_GetModuleName((PyTypeObject *)type); -} - +// Test for PyType_GetDict() static PyObject * test_get_type_dict(PyObject *self, PyObject *Py_UNUSED(ignored)) { - /* Test for PyType_GetDict */ - // Assert ints have a `to_bytes` method PyObject *long_dict = PyType_GetDict(&PyLong_Type); assert(long_dict); @@ -77,6 +42,7 @@ test_get_type_dict(PyObject *self, PyObject *Py_UNUSED(ignored)) } +// Test PyType_GetSlot() static PyObject * test_get_statictype_slots(PyObject *self, PyObject *Py_UNUSED(ignored)) { @@ -135,14 +101,12 @@ test_get_statictype_slots(PyObject *self, PyObject *Py_UNUSED(ignored)) // Get type->tp_version_tag static PyObject * -type_get_version(PyObject *self, PyObject *type) +type_get_version(PyObject *self, PyObject *arg) { - if (!PyType_Check(type)) { - PyErr_SetString(PyExc_TypeError, "argument must be a type"); - return NULL; - } - PyObject *res = PyLong_FromUnsignedLong( - ((PyTypeObject *)type)->tp_version_tag); + NULLABLE(arg); + PyTypeObject *type = (PyTypeObject*)arg; + + PyObject *res = PyLong_FromUnsignedLong(type->tp_version_tag); if (res == NULL) { assert(PyErr_Occurred()); return NULL; @@ -150,27 +114,12 @@ type_get_version(PyObject *self, PyObject *type) return res; } -static PyObject * -type_modified(PyObject *self, PyObject *arg) -{ - if (!PyType_Check(arg)) { - PyErr_SetString(PyExc_TypeError, "argument must be a type"); - return NULL; - } - PyTypeObject *type = (PyTypeObject*)arg; - - PyType_Modified(type); - Py_RETURN_NONE; -} - +// Test PyUnstable_Type_AssignVersionTag() static PyObject * type_assign_version(PyObject *self, PyObject *arg) { - if (!PyType_Check(arg)) { - PyErr_SetString(PyExc_TypeError, "argument must be a type"); - return NULL; - } + NULLABLE(arg); PyTypeObject *type = (PyTypeObject*)arg; int res = PyUnstable_Type_AssignVersionTag(type); @@ -178,13 +127,11 @@ type_assign_version(PyObject *self, PyObject *arg) } +// Get PyTypeObject.tp_bases static PyObject * type_get_tp_bases(PyObject *self, PyObject *arg) { - if (!PyType_Check(arg)) { - PyErr_SetString(PyExc_TypeError, "argument must be a type"); - return NULL; - } + NULLABLE(arg); PyTypeObject *type = (PyTypeObject*)arg; PyObject *bases = type->tp_bases; @@ -194,16 +141,15 @@ type_get_tp_bases(PyObject *self, PyObject *arg) return Py_NewRef(bases); } + +// Get PyTypeObject.tp_mro static PyObject * type_get_tp_mro(PyObject *self, PyObject *arg) { - if (!PyType_Check(arg)) { - PyErr_SetString(PyExc_TypeError, "argument must be a type"); - return NULL; - } + NULLABLE(arg); PyTypeObject *type = (PyTypeObject*)arg; - PyObject *mro = ((PyTypeObject *)type)->tp_mro; + PyObject *mro = type->tp_mro; if (mro == NULL) { Py_RETURN_NONE; } @@ -211,41 +157,38 @@ type_get_tp_mro(PyObject *self, PyObject *arg) } -static PyObject * -type_freeze(PyObject *module, PyObject *arg) -{ - if (!PyType_Check(arg)) { - PyErr_SetString(PyExc_TypeError, "argument must be a type"); - return NULL; - } - PyTypeObject *type = (PyTypeObject*)arg; - - if (PyType_Freeze(type) < 0) { - return NULL; - } - Py_RETURN_NONE; -} - - static PyMethodDef test_methods[] = { - {"get_heaptype_for_name", get_heaptype_for_name, METH_NOARGS}, - {"get_type_name", get_type_name, METH_O}, - {"get_type_qualname", get_type_qualname, METH_O}, - {"get_type_fullyqualname", get_type_fullyqualname, METH_O}, - {"get_type_module_name", get_type_module_name, METH_O}, {"test_get_type_dict", test_get_type_dict, METH_NOARGS}, {"test_get_statictype_slots", test_get_statictype_slots, METH_NOARGS}, {"type_get_version", type_get_version, METH_O, PyDoc_STR("type->tp_version_tag")}, - {"type_modified", type_modified, METH_O, PyDoc_STR("PyType_Modified")}, {"type_assign_version", type_assign_version, METH_O, PyDoc_STR("PyUnstable_Type_AssignVersionTag")}, {"type_get_tp_bases", type_get_tp_bases, METH_O}, {"type_get_tp_mro", type_get_tp_mro, METH_O}, - {"type_freeze", type_freeze, METH_O}, {NULL}, }; int _PyTestCapi_Init_Type(PyObject *m) { - return PyModule_AddFunctions(m, test_methods); + if (PyModule_AddFunctions(m, test_methods) < 0) { + return -1; + } + +#define ADD_INT(macro) \ + do { \ + if (PyModule_AddIntConstant(m, #macro, macro) < 0) { \ + return -1; \ + } \ + } while (0) + + // Flags excluded from the limited C API + ADD_INT(_Py_TPFLAGS_STATIC_BUILTIN); + ADD_INT(Py_TPFLAGS_INLINE_VALUES); + ADD_INT(Py_TPFLAGS_MANAGED_WEAKREF); + ADD_INT(Py_TPFLAGS_MANAGED_DICT); + ADD_INT(Py_TPFLAGS_SEQUENCE); + ADD_INT(Py_TPFLAGS_MAPPING); + +#undef ADD_INT + return 0; } diff --git a/Modules/_testlimitedcapi.c b/Modules/_testlimitedcapi.c index 0a562ea9c03110..b602d3219f5a52 100644 --- a/Modules/_testlimitedcapi.c +++ b/Modules/_testlimitedcapi.c @@ -107,5 +107,8 @@ PyInit__testlimitedcapi(void) if (_PyTestLimitedCAPI_Init_Run(mod) < 0) { return NULL; } + if (_PyTestLimitedCAPI_Init_Type(mod) < 0) { + return NULL; + } return mod; } diff --git a/Modules/_testlimitedcapi/parts.h b/Modules/_testlimitedcapi/parts.h index 32c1bbc1b71c97..35fbdea1d434e0 100644 --- a/Modules/_testlimitedcapi/parts.h +++ b/Modules/_testlimitedcapi/parts.h @@ -48,5 +48,6 @@ int _PyTestLimitedCAPI_Init_Version(PyObject *module); int _PyTestLimitedCAPI_Init_File(PyObject *module); int _PyTestLimitedCAPI_Init_Weakref(PyObject *module); int _PyTestLimitedCAPI_Init_Run(PyObject *module); +int _PyTestLimitedCAPI_Init_Type(PyObject *module); #endif // Py_TESTLIMITEDCAPI_PARTS_H diff --git a/Modules/_testlimitedcapi/type.c b/Modules/_testlimitedcapi/type.c new file mode 100644 index 00000000000000..640468ae08dcbb --- /dev/null +++ b/Modules/_testlimitedcapi/type.c @@ -0,0 +1,222 @@ +// Thin wrappers to PyType functions. +// Do no check PyType_Check() so Python tests can pass arbitrary objects, +// even if it's likely to crash. + +// Need limited C API version 3.14 for PyType_Freeze() +#include "pyconfig.h" // Py_GIL_DISABLED +#if !defined(Py_GIL_DISABLED) && !defined(Py_LIMITED_API) +# define Py_LIMITED_API 0x030e0000 +#endif + +#include "parts.h" +#include "util.h" + + +static PyType_Slot HeapTypeNameType_slots[] = { + {0}, +}; + +static PyType_Spec HeapTypeNameType_Spec = { + .name = "_testcapi.HeapTypeNameType", + .basicsize = sizeof(PyObject), + .flags = Py_TPFLAGS_DEFAULT, + .slots = HeapTypeNameType_slots, +}; + + +// Test PyType_FromSpec() with a minimum PyType_Spec +static PyObject* +get_heaptype_for_name(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + return PyType_FromSpec(&HeapTypeNameType_Spec); +} + + +// Test PyType_GetName() +static PyObject* +get_type_name(PyObject *self, PyObject *arg) +{ + NULLABLE(arg); + PyTypeObject *type = (PyTypeObject*)arg; + + return PyType_GetName(type); +} + + +// Test PyType_GetQualName() +static PyObject* +get_type_qualname(PyObject *self, PyObject *arg) +{ + NULLABLE(arg); + PyTypeObject *type = (PyTypeObject*)arg; + + return PyType_GetQualName(type); +} + + +// Test PyType_GetFullyQualifiedName() +static PyObject* +get_type_fullyqualname(PyObject *self, PyObject *arg) +{ + NULLABLE(arg); + PyTypeObject *type = (PyTypeObject*)arg; + + return PyType_GetFullyQualifiedName(type); +} + + +// Test PyType_GetModuleName() +static PyObject* +get_type_module_name(PyObject *self, PyObject *arg) +{ + NULLABLE(arg); + PyTypeObject *type = (PyTypeObject*)arg; + + return PyType_GetModuleName(type); +} + + +// Test PyType_Modified() +static PyObject* +type_modified(PyObject *self, PyObject *arg) +{ + NULLABLE(arg); + PyTypeObject *type = (PyTypeObject*)arg; + + PyType_Modified(type); + Py_RETURN_NONE; +} + + +// Test PyType_Ready() +static PyObject* +type_ready(PyObject *self, PyObject *arg) +{ + assert(!PyErr_Occurred()); + NULLABLE(arg); + PyTypeObject *type = (PyTypeObject*)arg; + + if (PyType_Ready(type) < 0) { + assert(PyErr_Occurred()); + return NULL; + } + assert(!PyErr_Occurred()); + Py_RETURN_NONE; +} + + +// Test PyType_Freeze() +static PyObject * +type_freeze(PyObject *module, PyObject *arg) +{ + NULLABLE(arg); + PyTypeObject *type = (PyTypeObject*)arg; + + if (PyType_Freeze(type) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + + +// Test PyType_ClearCache() +static PyObject * +type_clearcache(PyObject *module, PyObject *Py_UNUSED(arg)) +{ + // Since Python 3.16, PyType_ClearCache() is a no-op as the type cache is + // now implemented per-type. It still returns the current version tag. + unsigned int version_tag = PyType_ClearCache(); + assert(!PyErr_Occurred()); + return PyLong_FromUnsignedLong(version_tag); +} + + +// Test PyType_GetFlags() +static PyObject * +type_getflags(PyObject *module, PyObject *arg) +{ + NULLABLE(arg); + PyTypeObject *type = (PyTypeObject*)arg; + + unsigned long flags = PyType_GetFlags(type); + assert(!PyErr_Occurred()); + return PyLong_FromUnsignedLong(flags); +} + + +// Test PyType_IsSubtype() +static PyObject * +type_issubtype(PyObject *module, PyObject *args) +{ + PyTypeObject *type1, *type2; + if (!PyArg_ParseTuple(args, "O!O!", + &PyType_Type, &type1, + &PyType_Type, &type2)) { + return NULL; + } + + int is_subtype = PyType_IsSubtype(type1, type2); + return PyLong_FromLong(is_subtype); +} + + +static PyMethodDef test_methods[] = { + {"get_heaptype_for_name", get_heaptype_for_name, METH_NOARGS}, + {"get_type_name", get_type_name, METH_O}, + {"get_type_qualname", get_type_qualname, METH_O}, + {"get_type_fullyqualname", get_type_fullyqualname, METH_O}, + {"get_type_module_name", get_type_module_name, METH_O}, + {"type_ready", type_ready, METH_O}, + {"type_modified", type_modified, METH_O}, + {"type_freeze", type_freeze, METH_O}, + {"type_clearcache", type_clearcache, METH_NOARGS}, + {"type_getflags", type_getflags, METH_O}, + {"type_issubtype", type_issubtype, METH_VARARGS}, + {NULL}, +}; + +int +_PyTestLimitedCAPI_Init_Type(PyObject *m) +{ + if (PyModule_AddFunctions(m, test_methods) < 0) { + return -1; + } + +#define ADD_INT(macro) \ + do { \ + if (PyModule_AddIntConstant(m, #macro, macro) < 0) { \ + return -1; \ + } \ + } while (0) + + ADD_INT(Py_TPFLAGS_DEFAULT); + + ADD_INT(Py_TPFLAGS_HAVE_FINALIZE); + ADD_INT(Py_TPFLAGS_HAVE_GC); + ADD_INT(Py_TPFLAGS_HAVE_VERSION_TAG); + ADD_INT(Py_TPFLAGS_HAVE_VECTORCALL); + + ADD_INT(Py_TPFLAGS_DISALLOW_INSTANTIATION ); + ADD_INT(Py_TPFLAGS_IMMUTABLETYPE); + ADD_INT(Py_TPFLAGS_HEAPTYPE); + ADD_INT(Py_TPFLAGS_BASETYPE); + ADD_INT(Py_TPFLAGS_READY); + ADD_INT(Py_TPFLAGS_READYING); + ADD_INT(Py_TPFLAGS_METHOD_DESCRIPTOR); + ADD_INT(Py_TPFLAGS_VALID_VERSION_TAG); + ADD_INT(Py_TPFLAGS_IS_ABSTRACT); + ADD_INT(_Py_TPFLAGS_MATCH_SELF); + ADD_INT(Py_TPFLAGS_ITEMS_AT_END); + + ADD_INT(Py_TPFLAGS_LONG_SUBCLASS); + ADD_INT(Py_TPFLAGS_LIST_SUBCLASS); + ADD_INT(Py_TPFLAGS_TUPLE_SUBCLASS); + ADD_INT(Py_TPFLAGS_BYTES_SUBCLASS); + ADD_INT(Py_TPFLAGS_UNICODE_SUBCLASS); + ADD_INT(Py_TPFLAGS_DICT_SUBCLASS); + ADD_INT(Py_TPFLAGS_BASE_EXC_SUBCLASS); + ADD_INT(Py_TPFLAGS_TYPE_SUBCLASS); + +#undef ADD_INT + return 0; +} diff --git a/PCbuild/_testlimitedcapi.vcxproj b/PCbuild/_testlimitedcapi.vcxproj index 785bb151e08129..c2bdea923f0f6d 100644 --- a/PCbuild/_testlimitedcapi.vcxproj +++ b/PCbuild/_testlimitedcapi.vcxproj @@ -120,6 +120,7 @@ + diff --git a/PCbuild/_testlimitedcapi.vcxproj.filters b/PCbuild/_testlimitedcapi.vcxproj.filters index 51dc9950a10376..4c999949474b97 100644 --- a/PCbuild/_testlimitedcapi.vcxproj.filters +++ b/PCbuild/_testlimitedcapi.vcxproj.filters @@ -36,6 +36,7 @@ + diff --git a/README.rst b/README.rst index 8505ec0a1e89dc..0cafee734e1073 100644 --- a/README.rst +++ b/README.rst @@ -91,7 +91,7 @@ For example:: (This will fail if you *also* built at the top-level directory. You should do a ``make clean`` at the top-level first.) -To get an optimized build of Python, ``configure --enable-optimizations`` +To get an optimized build of Python, run ``configure --enable-optimizations`` before you run ``make``. This sets the default make targets up to enable Profile Guided Optimization (PGO) and may be used to auto-enable Link Time Optimization (LTO) on some platforms. For more details, see the sections