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

Support Python 3.10 #250

Merged
merged 3 commits into from
Nov 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
# R0401 cyclic-import
# R0205 useless-object-inheritance
# R1717 consider-using-dict-comprehension
disable=W0511,C0111,C0103,C0415,I0011,R0913,R0903,R0401,R0205,R1717,useless-suppression
disable=W0511,C0111,C0103,C0415,I0011,R0913,R0903,R0401,R0205,R1717,useless-suppression,
consider-using-f-string
Copy link
Member Author

Choose a reason for hiding this comment

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

There are countless places where str.format is used. We should allow it.


[FORMAT]
max-line-length=120
Expand All @@ -23,4 +24,3 @@ max-branches=20

[SIMILARITIES]
min-similarity-lines=10

1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ python:
- '3.7'
- '3.8'
- '3.9'
- '3.10'
install: pip install tox-travis
script: tox
11 changes: 7 additions & 4 deletions azure-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
timeoutInMinutes: 20

pool:
vmImage: 'ubuntu-16.04'
vmImage: 'ubuntu-20.04'
strategy:
matrix:
Python36:
Expand All @@ -32,6 +32,9 @@ jobs:
Python39:
python.version: '3.9'
tox_env: 'py39'
Python310:
python.version: '3.10'
tox_env: 'py310'
steps:
- task: UsePythonVersion@0
displayName: 'Use Python $(python.version)'
Expand All @@ -50,12 +53,12 @@ jobs:
- job: BuildPythonWheel
condition: succeeded()
pool:
image: 'ubuntu-16.04'
vmImage: 'ubuntu-20.04'
steps:
- task: UsePythonVersion@0
displayName: Use Python 3.6
displayName: Use Python 3.9
inputs:
versionSpec: 3.6
versionSpec: 3.9
- bash: |
set -ev

Expand Down
4 changes: 2 additions & 2 deletions knack/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,9 @@ def __init__(self, cli_ctx=None, command_cls=CLICommand, excluded_command_handle
self.skip_applicability = False
self.excluded_command_handler_args = excluded_command_handler_args
# A command table is a dictionary of name -> CLICommand instances
self.command_table = dict()
self.command_table = {}
# A command group table is a dictionary of names -> CommandGroup instances
self.command_group_table = dict()
self.command_group_table = {}
# An argument registry stores all arguments for commands
self.argument_registry = ArgumentRegistry()
self.extra_argument_registry = defaultdict(lambda: {})
Expand Down
6 changes: 4 additions & 2 deletions knack/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

_UNSET = object()

CONFIG_FILE_ENCODING = 'utf-8'


def get_config_parser():
return configparser.ConfigParser() # keep this for backward compatibility
Expand Down Expand Up @@ -190,7 +192,7 @@ def __init__(self, config_dir, config_path, config_comment=None):
self.config_comment = config_comment
self.config_parser = configparser.ConfigParser()
if os.path.exists(config_path):
self.config_parser.read(config_path)
self.config_parser.read(config_path, encoding=CONFIG_FILE_ENCODING)

def items(self, section):
return self.config_parser.items(section) if self.config_parser else []
Expand Down Expand Up @@ -220,7 +222,7 @@ def getboolean(self, section, option):

def set(self, config):
ensure_dir(self.config_dir)
with open(self.config_path, 'w') as configfile:
with open(self.config_path, 'w', encoding=CONFIG_FILE_ENCODING) as configfile:
Copy link
Member Author

Choose a reason for hiding this comment

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

pylint warns:

knack\config.py:225:13: W1514: Using open without explicitly specifying an encoding (unspecified-encoding)

See https://pylint.pycqa.org/en/latest/technical_reference/features.html#stdlib-checker-messages

if self.config_comment:
configfile.write(self.config_comment + '\n')
config.write(configfile)
Expand Down
36 changes: 18 additions & 18 deletions knack/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ def __init__(self, name_source, description, required, choices=None, default=Non

def update_from_data(self, data):
if self.name != data.get('name'):
raise HelpAuthoringException(u"mismatched name {} vs. {}"
raise HelpAuthoringException("mismatched name {} vs. {}"
.format(self.name,
data.get('name')))

Expand Down Expand Up @@ -394,7 +394,7 @@ def _print_header(self, cli_name, help_file):
_print_indent('Command' if help_file.type == 'command' else 'Group', indent)

indent += 1
LINE_FORMAT = u'{cli}{name}{separator}{summary}'
LINE_FORMAT = '{cli}{name}{separator}{summary}'
line = LINE_FORMAT.format(
cli=cli_name,
name=' ' + help_file.command if help_file.command else '',
Expand All @@ -421,7 +421,7 @@ def _build_long_summary(item):

def _print_groups(self, help_file):

LINE_FORMAT = u'{name}{padding}{tags}{separator}{summary}'
LINE_FORMAT = '{name}{padding}{tags}{separator}{summary}'
indent = 1

self.max_line_len = 0
Expand Down Expand Up @@ -496,25 +496,25 @@ def _print_items(layouts):

@staticmethod
def _get_choices_defaults_sources_str(p):
choice_str = u' Allowed values: {}.'.format(', '.join(sorted([str(x) for x in p.choices]))) \
choice_str = ' Allowed values: {}.'.format(', '.join(sorted([str(x) for x in p.choices]))) \
if p.choices else ''
default_str = u' Default: {}.'.format(p.default) \
default_str = ' Default: {}.'.format(p.default) \
if p.default and p.default != argparse.SUPPRESS else ''
value_sources_str = u' Values from: {}.'.format(', '.join(p.value_sources)) \
value_sources_str = ' Values from: {}.'.format(', '.join(p.value_sources)) \
if p.value_sources else ''
return u'{}{}{}'.format(choice_str, default_str, value_sources_str)
return '{}{}{}'.format(choice_str, default_str, value_sources_str)

@staticmethod
def print_description_list(help_files):
indent = 1
max_length = max(len(f.name) for f in help_files) if help_files else 0
for help_file in sorted(help_files, key=lambda h: h.name):
column_indent = max_length - len(help_file.name)
_print_indent(u'{}{}{}'.format(help_file.name,
' ' * column_indent,
FIRST_LINE_PREFIX + help_file.short_summary
if help_file.short_summary
else ''),
_print_indent('{}{}{}'.format(help_file.name,
' ' * column_indent,
FIRST_LINE_PREFIX + help_file.short_summary
if help_file.short_summary
else ''),
indent,
_get_hanging_indent(max_length, indent))

Expand All @@ -524,14 +524,14 @@ def _print_examples(help_file):
_print_indent('Examples', indent)
for e in help_file.examples:
indent = 1
_print_indent(u'{0}'.format(e.name), indent)
_print_indent('{0}'.format(e.name), indent)
indent = 2
_print_indent(u'{0}'.format(e.text), indent)
_print_indent('{0}'.format(e.text), indent)
print('')

def _print_arguments(self, help_file): # pylint: disable=too-many-statements

LINE_FORMAT = u'{name}{padding}{tags}{separator}{short_summary}'
LINE_FORMAT = '{name}{padding}{tags}{separator}{short_summary}'
indent = 1
self.max_line_len = 0

Expand Down Expand Up @@ -644,9 +644,9 @@ def _build_long_summary(item):
group_registry = ArgumentGroupRegistry([p.group_name for p in help_file.parameters if p.group_name])

def _get_parameter_key(parameter):
return u'{}{}{}'.format(group_registry.get_group_priority(parameter.group_name),
str(not parameter.required),
parameter.name)
return '{}{}{}'.format(group_registry.get_group_priority(parameter.group_name),
str(not parameter.required),
parameter.name)

parameter_layouts = _layout_items(help_file.parameters)
_print_items(parameter_layouts)
Expand Down
3 changes: 2 additions & 1 deletion knack/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ def _expand_prefixed_files(args):
if args[arg].startswith('@'):
try:
logger.debug('Attempting to read file %s', args[arg][1:])
with open(args[arg][1:], 'r') as f:
# Use the default system encoding: https://docs.python.org/3/library/functions.html#open
with open(args[arg][1:], 'r') as f: # pylint: disable=unspecified-encoding
content = f.read()
args[arg] = content
except IOError:
Expand Down
6 changes: 3 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
argcomplete==1.12.2
colorama==0.4.4
flake8==3.8.4
flake8==4.0.1
jmespath==0.10.0
Pygments==2.8.1
pylint==2.7.2
pytest==6.2.2
pylint==2.11.1
Copy link
Member Author

Choose a reason for hiding this comment

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

New pylint is more strict on grammar. We need to fix these additional check failures.

pytest==6.2.5
Copy link
Member Author

@jiasli jiasli Oct 13, 2021

Choose a reason for hiding this comment

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

Old pytest fails in Python 3.10: pytest-dev/pytest#8546 (comment)

=============================================== short test summary info ===============================================
ERROR tests/test_cli_scenarios.py - TypeError: required field "lineno" missing from alias
ERROR tests/test_command_registration.py - TypeError: required field "lineno" missing from alias
ERROR tests/test_command_with_configured_defaults.py - TypeError: required field "lineno" missing from alias
ERROR tests/test_completion.py - TypeError: required field "lineno" missing from alias
ERROR tests/test_config.py - TypeError: required field "lineno" missing from alias
ERROR tests/test_deprecation.py - TypeError: required field "lineno" missing from alias
ERROR tests/test_experimental.py - TypeError: required field "lineno" missing from alias
ERROR tests/test_help.py - TypeError: required field "lineno" missing from alias
ERROR tests/test_introspection.py - TypeError: required field "lineno" missing from alias
ERROR tests/test_log.py - TypeError: required field "lineno" missing from alias
ERROR tests/test_output.py - TypeError: required field "lineno" missing from alias
ERROR tests/test_parser.py - TypeError: required field "lineno" missing from alias
ERROR tests/test_preview.py - TypeError: required field "lineno" missing from alias
ERROR tests/test_prompting.py - TypeError: required field "lineno" missing from alias
ERROR tests/test_query.py - TypeError: required field "lineno" missing from alias
ERROR tests/test_util.py - TypeError: required field "lineno" missing from alias

PyYAML
tabulate==0.8.9
vcrpy==4.1.1
2 changes: 1 addition & 1 deletion scripts/license_verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def get_files_without_header():
for a_file in files:
if a_file.endswith('.py'):
cur_file_path = os.path.join(current_dir, a_file)
with open(cur_file_path, 'r') as f:
with open(cur_file_path, 'r', encoding='utf-8') as f:
file_text = f.read()

if len(file_text) > 0 and not contains_header(file_text):
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'License :: OSI Approved :: MIT License',
],
packages=['knack', 'knack.testsdk'],
Expand Down
4 changes: 2 additions & 2 deletions tests/test_deprecation.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def test_deprecate_command_group_help(self):
with self.assertRaises(SystemExit):
self.cli_ctx.invoke('-h'.split())
actual = self.io.getvalue()
expected = u"""
expected = """
Group
{}

Expand Down Expand Up @@ -429,7 +429,7 @@ def test_deprecate_options_execute_expired_non_deprecated(self):
""" Ensure non-expired options can be used without warning. """
self.cli_ctx.invoke('arg-test --arg1 foo --opt1 bar --opt5 foo'.split())
actual = self.io.getvalue()
self.assertTrue(u'--alt5' not in actual and u'--opt5' not in actual)
self.assertTrue('--alt5' not in actual and '--opt5' not in actual)

@redirect_io
def test_deprecate_options_execute_expiring(self):
Expand Down
2 changes: 1 addition & 1 deletion tests/test_experimental.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def test_experimental_command_group_help(self):
with self.assertRaises(SystemExit):
self.cli_ctx.invoke('-h'.split())
actual = self.io.getvalue()
expected = u"""
expected = """
Group
{}

Expand Down
2 changes: 1 addition & 1 deletion tests/test_preview.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def test_preview_command_group_help(self):
with self.assertRaises(SystemExit):
self.cli_ctx.invoke('-h'.split())
actual = self.io.getvalue()
expected = u"""
expected = """
Group
{}

Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist = py36,py37,py38,py39
envlist = py36,py37,py38,py39,py310
[testenv]
deps = -rrequirements.txt
commands=
Expand Down