Python 3.11 changes
In [Packaging] Support Python 3.11 by bebound · Pull Request #26923 · Azure/azure-cli (github.com) , I bumped azure-cli to use Python 3.11. We’ve bump the dependency in other PRs, I thought it should be a small PR, but in the end, a lot of changes are made.
args.getargspec
getargspec is dropped in 3.11. You can easily replaced it with getfullargspec . It returns FullArgSpec(args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations) instead of ArgSpec(args, varargs, keywords, defaults) So args, _, kw, _ = inspect.getargspec(fn) can be replaced by args, _, kw, *_ = inspect.getfullargspec(fn) However, getfullargspec is retained primarily for use in code that needs to maintain compatibility with the Python 2 inspect module API.
Note that
signature()and Signature Object provide the recommended API for callable introspection, and support additional behaviours (like positional-only arguments) that are sometimes encountered in extension module APIs. This function is retained primarily for use in code that needs to maintain compatibility with the Python 2inspectmodule API. –inspect — Inspect live objects — Python 3.11.4 documentation
The modern signature function provides the similar result but needs more modification:
import inspect
def testfunc(a, /, b=1, c=2, *args, kk, **kwargs):
pass
print(inspect.getfullargspec(testfunc))
print(inspect.signature(testfunc).parameters)
for i, j in inspect.signature(testfunc).parameters.items():
print(i, type(i), j, type(j), j.kind)
args, _, kw, *_ = inspect.getfullargspec(testfunc)
print(args, kw)
from inspect import Parameter
parameters = inspect.signature(testfunc).parameters
args = [k for k, v in parameters.items() if v.kind in {Parameter.POSITIONAL_OR_KEYWORD, Parameter.POSITIONAL_ONLY}]
kw = next(iter([k for k, v in parameters.items() if v.kind == Parameter.VAR_KEYWORD]), None)
print(args, kw)
FullArgSpec(args=['a', 'b', 'c'], varargs='args', varkw='kwargs', defaults=(1, 2), kwonlyargs=['kk'], kwonlydefaults=None, annotations={})
OrderedDict([('a', <Parameter "a">), ('b', <Parameter "b=1">), ('c', <Parameter "c=2">), ('args', <Parameter "*args">), ('kk', <Parameter "kk">), ('kwargs', <Parameter "**kwargs">)])
a <class 'str'> a <class 'inspect.Parameter'> POSITIONAL_ONLY
b <class 'str'> b=1 <class 'inspect.Parameter'> POSITIONAL_OR_KEYWORD
c <class 'str'> c=2 <class 'inspect.Parameter'> POSITIONAL_OR_KEYWORD
args <class 'str'> *args <class 'inspect.Parameter'> VAR_POSITIONAL
kk <class 'str'> kk <class 'inspect.Parameter'> KEYWORD_ONLY
kwargs <class 'str'> **kwargs <class 'inspect.Parameter'> VAR_KEYWORD
['a', 'b', 'c'] kwargs
['a', 'b', 'c'] kwargs
Enum __format__ change
There is some custom classes in azure-cli, which makes Foo.BAR=‘bar’. In 3.11, the [[https://docs.python.org/3/whatsnew/3.11.html#enum][Enum]] =__format__() changes, it returns the enum and member name (ex: Color.RED). (The __str__ method is the same as Python 3.10)
Changed
Enum.__format__()(the default forformat(),str.format()and f-strings) to always produce the same result asEnum.__str__(): for enums inheriting fromReprEnumit will be the member’s value; for all other enums it will be the enum and member name (e.g.Color.RED). –What’s New In Python 3.11
from enum import Enum
class Foo(str, Enum):
BAR = "bar"
# Python 3.10
f"{Foo.BAR}" # > bar
str(Foo.BAR) # > Foo.BAR
# Python 3.11
f"{Foo.BAR}" # > Foo.BAR
str(Foo.BAR) # > Foo.BAR
The standard way to replace Foo class is StrEnum
class Foo(StrEnum):
BAR = "bar"
# Python 3.11
f"{Foo.BAR}" # > bar
If you also use Bar(int, Enum), you can replace it with ReprEnum: Bar(int, ReprEnum).
unittest.Mock
The unittest module replace unittest.mock._importer with pkgutil.resolve_name in bpo-44686 replace unittest.mock._importer with pkgutil.resolve_name by graingert · Pull Request #18544 · python/cpython (github.com), which also introduces some changes.
Previously, it use __import__ to import the patch target, which does not check the module name. But pkgutil.resolve_name will check name first, thus mock.patch fails if the target is not a valid Python module name. For example, this statement fails in 3.11:
@mock.patch('azure.cli.command_modules.vm.aaz.2020_09_01_hybrid.network.vnet.List', _mock_network_client_with_existing_vnet_location)
as 2020_09_01_hybrid is not a valid variable name in Python.
_NAME_PATTERN = re.compile(f'^(?P<pkg>{dotted_words})'
f'(?P<cln>:(?P<obj>{dotted_words})?)?$',
re.UNICODE)
m = _NAME_PATTERN.match(name)
if not m:
> raise ValueError(f'invalid format: {name!r}')
E ValueError: invalid format: 'azure.cli.command_modules.vm.aaz.2020_09_01_hybrid.network.vnet'
As a workaround, mock.patch.object works.
vnet = import_module('azure.cli.command_modules.vm.aaz.2018_03_01_hybrid.network.vnet')
with mock.patch.object(vnet, 'List', _mock_network_client_with_existing_vnet):
The ultimate solution is fix module name.
argparse.ArgumentError
bpo-39716: Raise on conflicting subparser names. by anntzer · Pull Request #18605 · python/cpython (github.com) Raise an ArgumentError when the same subparser name is added twice.
import argparse
parser = argparse.ArgumentParser()
t = parser.add_subparsers()
t.add_parser('a')
t.add_parser('a')
The above code works on 3.10 but raises this error in 3.11:
Traceback (most recent call last):
File "C:\Users\kk\Developer\azure-cli\p.py", line 6, in <module>
t.add_parser('a')
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.1264.0_x64__qbz5n2kfra8p0\Lib\argparse.py", line 1192, in add_parser
raise ArgumentError(self, _('conflicting subparser: %s') % name)
argparse.ArgumentError: argument {a}: conflicting subparser: a