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

Conversation

dataflake
Copy link
Member

Fixes #292

In case you are wondering, the question marks next to the original comparison f_locals is locals() have existed since at least 2007 when the code was moved to GitHub. I am guessing the original author thought this identity test is questionable.

@dataflake dataflake self-assigned this May 13, 2024
# 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.

Copy link
Contributor

@d-maurer d-maurer left a comment

Choose a reason for hiding this comment

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

Apparently, the test should check getFrameInfo.

Like Tres, I think we can avoid the version dependency:
For the getFrameInfo check, it would be enough to determine that the returned f_locals war hightly likely derived from the current locals. I see different options:

  • compare against the result of sys._getframe (this could use is)
  • compare with DictEqual
  • put a marker variable into the local frame and check this marker variable

@dataflake
Copy link
Member Author

Like Tres, I think we can avoid the version dependency: For the getFrameInfo check, it would be enough to determine that the returned f_locals war hightly likely derived from the current locals. I see different options:

* compare against the result of `sys._getframe` (this could use `is`)

Fails under Python 3.13: self.assertTrue(f_locals is sys._getframe().f_locals)

* compare with `DictEqual`

Fails under Python 3.13 because f_locals is not a mapping and even coercing it with a dict call seems to return something that is not equal to the result of calling locals() there.

* put a marker variable into the local frame and check this marker variable

You mean like the following, which works under Python 3.13 and Python <3.13?

        # Set a variable to test against
        test_local_variable = 'foobar'

        self.assertIn('test_local_variable', locals())
        self.assertIn('test_local_variable', f_locals)

@d-maurer
Copy link
Contributor

d-maurer commented May 14, 2024 via email

@d-maurer
Copy link
Contributor

d-maurer commented May 14, 2024 via email

@dataflake
Copy link
Member Author

Yes, just a bit simplified: self.assertEqual(f_locals["test_local_variable"], "foobar")

This passes under Python 3.13 but fails with a KeyError under Python 3.12.

The following works on all supported Python versions:

        frame = sys._getframe()
        self.assertEqual(f_locals, frame.f_locals)
        self.assertEqual(f_locals, locals())

musicinmybrain added a commit to musicinmybrain/venusian that referenced this pull request May 15, 2024
The `frame.f_locals` is now a write-through proxy object of type
`FrameLocalsProxy`; see PEP 667. This fix is based on
zopefoundation/zope.interface#294 and
specifically on
zopefoundation/zope.interface#294 (comment).

Fixes Pylons#91.
musicinmybrain added a commit to musicinmybrain/venusian that referenced this pull request May 15, 2024
The `frame.f_locals` is now a write-through proxy object of type
`FrameLocalsProxy`; see PEP 667. This fix is based on
zopefoundation/zope.interface#294 and
specifically on
zopefoundation/zope.interface#294 (comment).

Fixes Pylons#91.
@musicinmybrain
Copy link

The following works on all supported Python versions:

        frame = sys._getframe()
        self.assertEqual(f_locals, frame.f_locals)
        self.assertEqual(f_locals, locals())

I just proposed this in Pylons/venusian#92 for venusian, which has the same issue.

@dataflake dataflake merged commit e613355 into master May 15, 2024
59 of 60 checks passed
@dataflake dataflake deleted the dataflake/pep667 branch May 15, 2024 20:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Test failures with Python 3.13.0b1
4 participants