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

Adjust for incompatible changes in Python 3.13b1 #294

Merged
merged 2 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
6.4 (unreleased)
================

- Adjust for incompatible changes in Python 3.13b1.
(`#292 <https://github.com/zopefoundation/zope.interface/issues/292>`)

- Build windows wheels on GHA.

6.3 (2024-04-12)
Expand Down
3 changes: 3 additions & 0 deletions src/zope/interface/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ def _normalize_name(name):
return name
raise TypeError("name must be a string or ASCII-only bytes")


PYPY = hasattr(sys, 'pypy_version_info')
_version = sys.version_info
IS_PY313_OR_GREATER = _version.major == 3 and _version.minor >= 13


def _c_optimizations_required():
Expand Down
3 changes: 3 additions & 0 deletions src/zope/interface/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,9 @@ def update_value(aname, aval):
# __static_attributes__: Python 3.13a6+
# https://github.com/python/cpython/pull/115913
'__static_attributes__',
# __firstlineno__: Python 3.13b1+
# https://github.com/python/cpython/pull/118475
'__firstlineno__',
)
and aval is not _decorator_non_return
}
Expand Down
14 changes: 13 additions & 1 deletion src/zope/interface/tests/test_advice.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,22 @@ def test_w_class(self):
self.assertTrue(d is advisory_testing.my_globals)

def test_inside_function_call(self):
from zope.interface._compat import IS_PY313_OR_GREATER
from zope.interface.advice import getFrameInfo
kind, module, f_locals, f_globals = getFrameInfo(sys._getframe())
self.assertEqual(kind, "function call")
self.assertTrue(f_locals is locals()) # ???

if IS_PY313_OR_GREATER:
# Python 3.13b1 implements PEP 667, which changes the type of
# ``f_locals`` from a mapping to a ``FrameLocalsProxy`` object
# while the return value of ``locals()`` is still a mapping, so
# they no longer point to the same object.
# See https://peps.python.org/pep-0667 and
# https://github.com/python/cpython/pull/115153
self.assertDictEqual(dict(f_locals), locals())
else:
self.assertTrue(f_locals is locals()) # ???
Copy link
Member

Choose a reason for hiding this comment

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

Do tests all pass if we just change to use the self.assertDictEqual assertion everywhere? Seems cleaner to me, anyway.

Copy link
Member Author

Choose a reason for hiding this comment

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

What I am seeing is that the test fails for Python<3.13 when running it normally. However, when I insert a pdb.set_trace() and step through it the test succeeds. Not sure what that means, a time.sleep(.5) doesn't have that effect, so it's not timing I guess:

Traceback (most recent call last):
  File "/Users/jens/src/zope/zope.interface/src/zope/interface/tests/test_advice.py", line 68, in test_inside_function_call
    self.assertDictEqual(dict(f_locals), locals())
AssertionError: {'sel[170 chars]0220>} != {'sel[170 chars]0220>, 'kind': 'function call', 'module': <mod[8946 chars]s'>}}
  {'IS_PY313_OR_GREATER': False,
+  'f_globals': {'FrameInfoTest': <class 'zope.interface.tests.test_advice.FrameInfoTest'>,
+                'Test_determineMetaclass': <class 'zope.interface.tests.test_advice.Test_determineMetaclass'>,
+                'Test_isClassAdvisor': <class 'zope.interface.tests.test_advice.Test_isClassAdvisor'>,
+                'Test_minimalBases': <class 'zope.interface.tests.test_advice.Test_minimalBases'>,
+                '_FUNKY_EXEC': 'import sys\n'
+                               'kind, module, f_locals, f_globals = '
+                               'getFrameInfo(sys._getframe())\n',
+                '__builtins__': {'ArithmeticError': <class 'ArithmeticError'>,
+                                 'AssertionError': <class 'AssertionError'>,
+                                 'AttributeError': <class 'AttributeError'>,
+                                 'BaseException': <class 'BaseException'>,
+                                 'BaseExceptionGroup': <class 'BaseExceptionGroup'>,
+                                 'BlockingIOError': <class 'BlockingIOError'>,
+                                 'BrokenPipeError': <class 'BrokenPipeError'>,
+                                 'BufferError': <class 'BufferError'>,
+                                 'BytesWarning': <class 'BytesWarning'>,
+                                 'ChildProcessError': <class 'ChildProcessError'>,
+                                 'ConnectionAbortedError': <class 'ConnectionAbortedError'>,
+                                 'ConnectionError': <class 'ConnectionError'>,
+                                 'ConnectionRefusedError': <class 'ConnectionRefusedError'>,
+                                 'ConnectionResetError': <class 'ConnectionResetError'>,
+                                 'DeprecationWarning': <class 'DeprecationWarning'>,
+                                 'EOFError': <class 'EOFError'>,
+                                 'Ellipsis': Ellipsis,
+                                 'EncodingWarning': <class 'EncodingWarning'>,
+                                 'EnvironmentError': <class 'OSError'>,
+                                 'Exception': <class 'Exception'>,
+                                 'ExceptionGroup': <class 'ExceptionGroup'>,
+                                 'False': False,
+                                 'FileExistsError': <class 'FileExistsError'>,
+                                 'FileNotFoundError': <class 'FileNotFoundError'>,
+                                 'FloatingPointError': <class 'FloatingPointError'>,
+                                 'FutureWarning': <class 'FutureWarning'>,
+                                 'GeneratorExit': <class 'GeneratorExit'>,
+                                 'IOError': <class 'OSError'>,
+                                 'ImportError': <class 'ImportError'>,
+                                 'ImportWarning': <class 'ImportWarning'>,
+                                 'IndentationError': <class 'IndentationError'>,
+                                 'IndexError': <class 'IndexError'>,
+                                 'InterruptedError': <class 'InterruptedError'>,
+                                 'IsADirectoryError': <class 'IsADirectoryError'>,
+                                 'KeyError': <class 'KeyError'>,
+                                 'KeyboardInterrupt': <class 'KeyboardInterrupt'>,
+                                 'LookupError': <class 'LookupError'>,
+                                 'MemoryError': <class 'MemoryError'>,
+                                 'ModuleNotFoundError': <class 'ModuleNotFoundError'>,
+                                 'NameError': <class 'NameError'>,
+                                 'None': None,
+                                 'NotADirectoryError': <class 'NotADirectoryError'>,
+                                 'NotImplemented': NotImplemented,
+                                 'NotImplementedError': <class 'NotImplementedError'>,
+                                 'OSError': <class 'OSError'>,
+                                 'OverflowError': <class 'OverflowError'>,
+                                 'PendingDeprecationWarning': <class 'PendingDeprecationWarning'>,
+                                 'PermissionError': <class 'PermissionError'>,
+                                 'ProcessLookupError': <class 'ProcessLookupError'>,
+                                 'RecursionError': <class 'RecursionError'>,
+                                 'ReferenceError': <class 'ReferenceError'>,
+                                 'ResourceWarning': <class 'ResourceWarning'>,
+                                 'RuntimeError': <class 'RuntimeError'>,
+                                 'RuntimeWarning': <class 'RuntimeWarning'>,
+                                 'StopAsyncIteration': <class 'StopAsyncIteration'>,
+                                 'StopIteration': <class 'StopIteration'>,
+                                 'SyntaxError': <class 'SyntaxError'>,
+                                 'SyntaxWarning': <class 'SyntaxWarning'>,
+                                 'SystemError': <class 'SystemError'>,
+                                 'SystemExit': <class 'SystemExit'>,
+                                 'TabError': <class 'TabError'>,
+                                 'TimeoutError': <class 'TimeoutError'>,
+                                 'True': True,
+                                 'TypeError': <class 'TypeError'>,
+                                 'UnboundLocalError': <class 'UnboundLocalError'>,
+                                 'UnicodeDecodeError': <class 'UnicodeDecodeError'>,
+                                 'UnicodeEncodeError': <class 'UnicodeEncodeError'>,
+                                 'UnicodeError': <class 'UnicodeError'>,
+                                 'UnicodeTranslateError': <class 'UnicodeTranslateError'>,
+                                 'UnicodeWarning': <class 'UnicodeWarning'>,
+                                 'UserWarning': <class 'UserWarning'>,
+                                 'ValueError': <class 'ValueError'>,
+                                 'Warning': <class 'Warning'>,
+                                 'ZeroDivisionError': <class 'ZeroDivisionError'>,
+                                 '__build_class__': <built-in function __build_class__>,
+                                 '__debug__': True,
+                                 '__doc__': 'Built-in functions, types, '
+                                            'exceptions, and other objects.\n'
+                                            '\n'
+                                            'This module provides direct access '
+                                            "to all 'built-in'\n"
+                                            'identifiers of Python; for '
+                                            'example, builtins.len is\n'
+                                            'the full name for the built-in '
+                                            'function len().\n'
+                                            '\n'
+                                            'This module is not normally '
+                                            'accessed explicitly by most\n'
+                                            'applications, but can be useful in '
+                                            'modules that provide\n'
+                                            'objects with the same name as a '
+                                            'built-in value, but in\n'
+                                            'which the built-in of that name is '
+                                            'also needed.',
+                                 '__import__': <built-in function __import__>,
+                                 '__loader__': <class '_frozen_importlib.BuiltinImporter'>,
+                                 '__name__': 'builtins',
+                                 '__package__': '',
+                                 '__spec__': ModuleSpec(name='builtins', loader=<class '_frozen_importlib.BuiltinImporter'>, origin='built-in'),
+                                 'abs': <built-in function abs>,
+                                 'aiter': <built-in function aiter>,
+                                 'all': <built-in function all>,
+                                 'anext': <built-in function anext>,
+                                 'any': <built-in function any>,
+                                 'ascii': <built-in function ascii>,
+                                 'bin': <built-in function bin>,
+                                 'bool': <class 'bool'>,
+                                 'breakpoint': <built-in function breakpoint>,
+                                 'bytearray': <class 'bytearray'>,
+                                 'bytes': <class 'bytes'>,
+                                 'callable': <built-in function callable>,
+                                 'chr': <built-in function chr>,
+                                 'classmethod': <class 'classmethod'>,
+                                 'compile': <built-in function compile>,
+                                 'complex': <class 'complex'>,
+                                 'copyright': Copyright (c) 2001-2023 Python Software Foundation.
+ All Rights Reserved.
+
+ Copyright (c) 2000 BeOpen.com.
+ All Rights Reserved.
+
+ Copyright (c) 1995-2001 Corporation for National Research Initiatives.
+ All Rights Reserved.
+
+ Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.
+ All Rights Reserved.,
+                                 '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.,
+                                 'delattr': <built-in function delattr>,
+                                 'dict': <class 'dict'>,
+                                 'dir': <built-in function dir>,
+                                 'divmod': <built-in function divmod>,
+                                 'enumerate': <class 'enumerate'>,
+                                 'eval': <built-in function eval>,
+                                 'exec': <built-in function exec>,
+                                 'exit': Use exit() or Ctrl-D (i.e. EOF) to exit,
+                                 'filter': <class 'filter'>,
+                                 'float': <class 'float'>,
+                                 'format': <built-in function format>,
+                                 'frozenset': <class 'frozenset'>,
+                                 'getattr': <built-in function getattr>,
+                                 'globals': <built-in function globals>,
+                                 'hasattr': <built-in function hasattr>,
+                                 'hash': <built-in function hash>,
+                                 'help': Type help() for interactive help, or help(object) for help about object.,
+                                 'hex': <built-in function hex>,
+                                 'id': <built-in function id>,
+                                 'input': <built-in function input>,
+                                 'int': <class 'int'>,
+                                 'isinstance': <built-in function isinstance>,
+                                 'issubclass': <built-in function issubclass>,
+                                 'iter': <built-in function iter>,
+                                 'len': <built-in function len>,
+                                 'license': Type license() to see the full license text,
+                                 'list': <class 'list'>,
+                                 'locals': <built-in function locals>,
+                                 'map': <class 'map'>,
+                                 'max': <built-in function max>,
+                                 'memoryview': <class 'memoryview'>,
+                                 'min': <built-in function min>,
+                                 'next': <built-in function next>,
+                                 'object': <class 'object'>,
+                                 'oct': <built-in function oct>,
+                                 'open': <built-in function open>,
+                                 'ord': <built-in function ord>,
+                                 'pow': <built-in function pow>,
+                                 'print': <built-in function print>,
+                                 'property': <class 'property'>,
+                                 'quit': Use quit() or Ctrl-D (i.e. EOF) to exit,
+                                 'range': <class 'range'>,
+                                 'repr': <built-in function repr>,
+                                 'reversed': <class 'reversed'>,
+                                 'round': <built-in function round>,
+                                 'set': <class 'set'>,
+                                 'setattr': <built-in function setattr>,
+                                 'slice': <class 'slice'>,
+                                 'sorted': <built-in function sorted>,
+                                 'staticmethod': <class 'staticmethod'>,
+                                 'str': <class 'str'>,
+                                 'sum': <built-in function sum>,
+                                 'super': <class 'super'>,
+                                 'tuple': <class 'tuple'>,
+                                 'type': <class 'type'>,
+                                 'vars': <built-in function vars>,
+                                 'zip': <class 'zip'>},
+                '__cached__': '/Users/jens/src/zope/zope.interface/src/zope/interface/tests/__pycache__/test_advice.cpython-312.pyc',
+                '__doc__': 'Tests for advice\n'
+                           '\n'
+                           'This module was adapted from '
+                           "'protocols.tests.advice', part of the Python\n"
+                           'Enterprise Application Kit (PEAK).  Please notify '
+                           'the PEAK authors\n'
+                           '(pje@telecommunity.com and tsarna@sarna.org) if '
+                           'bugs are found or\n'
+                           'Zope-specific changes are required, so that the '
+                           'PEAK version of this module\n'
+                           'can be kept in sync.\n'
+                           '\n'
+                           'PEAK is a Python application framework that '
+                           'interoperates with (but does\n'
+                           'not require) Zope 3 and Twisted.  It provides tools '
+                           'for manipulating UML\n'
+                           'models, object-relational persistence, '
+                           'aspect-oriented programming, and more.\n'
+                           'Visit the PEAK home page at '
+                           'http://peak.telecommunity.com for more '
+                           'information.\n',
+                '__file__': '/Users/jens/src/zope/zope.interface/src/zope/interface/tests/test_advice.py',
+                '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x105d917f0>,
+                '__name__': 'zope.interface.tests.test_advice',
+                '__package__': 'zope.interface.tests',
+                '__spec__': ModuleSpec(name='zope.interface.tests.test_advice', loader=<_frozen_importlib_external.SourceFileLoader object at 0x105d917f0>, origin='/Users/jens/src/zope/zope.interface/src/zope/interface/tests/test_advice.py'),
+                'sys': <module 'sys' (built-in)>,
+                'unittest': <module 'unittest' from '/Users/jens/src/python/Python-3.12.1/lib/python3.12/unittest/__init__.py'>},
+  'f_locals': <Recursion on dict with id=4393161536>,
   'getFrameInfo': <function getFrameInfo at 0x106080220>,
+  'kind': 'function call',
+  'module': <module 'zope.interface.tests.test_advice' from '/Users/jens/src/zope/zope.interface/src/zope/interface/tests/test_advice.py'>,
   'self': <zope.interface.tests.test_advice.FrameInfoTest testMethod=test_inside_function_call>}

Copy link
Member Author

Choose a reason for hiding this comment

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

This looks like an issue about how the f_locals returned from the getFrameInfo call at the top and the mapping from the later call to locals() do or do not get updated with the local variables that get added during the test method run.


for d in module.__dict__, f_globals:
self.assertTrue(d is globals())

Expand Down
Loading