diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..ba1b7f19 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,31 @@ +# Basic dependabot.yml file with minimum configuration for two package managers + +version: 2 +updates: + # Enable version updates for python + - package-ecosystem: "pip" + directory: ".github/scripts/" + schedule: + interval: "monthly" + labels: ["dependabot"] + pull-request-branch-name: + separator: "-" + open-pull-requests-limit: 5 + reviewers: + - "dbieber" + + # Enable version updates for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + groups: + gh-actions: + patterns: + - "*" # Check all dependencies + labels: ["dependabot"] + pull-request-branch-name: + separator: "-" + open-pull-requests-limit: 5 + reviewers: + - "dbieber" diff --git a/.github/scripts/build.sh b/.github/scripts/build.sh index 1f9ed766..111257ae 100755 --- a/.github/scripts/build.sh +++ b/.github/scripts/build.sh @@ -17,7 +17,7 @@ # Exit when any command fails. set -e -PYTHON_VERSION=${PYTHON_VERSION:-2.7} +PYTHON_VERSION=${PYTHON_VERSION:-3.7} pip install -U -r .github/scripts/requirements.txt python setup.py develop diff --git a/.github/scripts/requirements.txt b/.github/scripts/requirements.txt index 13880c9c..a5648989 100644 --- a/.github/scripts/requirements.txt +++ b/.github/scripts/requirements.txt @@ -1,13 +1,9 @@ -setuptools <65.7.0 ; python_version == '2.7' -setuptools <=69.1.1 ; python_version >= '3.8' -pip <23.0 ; python_version == '2.7' -pip ; python_version >= '3.5' -pylint <2.15.10 -pytest <=7.2.1 +setuptools <=75.1.0 +pip +pylint <3.2.8 +pytest <=8.3.3 pytest-pylint <=1.1.2 -pytest-runner <6.0.0 -termcolor <2.2.0 -hypothesis <6.62.0 -python-Levenshtein <0.20.9 ; python_version == '2.7' -levenshtein <=0.25.0 ; python_version >= '3.5' -mock <5.0.0 +pytest-runner <7.0.0 +termcolor <2.5.0 +hypothesis <6.113.0 +levenshtein <=0.26.0 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7a9727cb..f5978e23 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,22 +1,26 @@ name: Python Fire -on: [push, pull_request] +on: + push: + branches: ["master"] + pull_request: + branches: ["master"] jobs: build: runs-on: ubuntu-20.04 strategy: matrix: - python-version: ["3.5", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] steps: # Checkout the repo. - name: Checkout Python Fire repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Set up Python environment. - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} diff --git a/docs/guide.md b/docs/guide.md index 44d8a46d..444a76ff 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -30,7 +30,7 @@ the program to the command line. import fire def hello(name): - return 'Hello {name}!'.format(name=name) + return f'Hello {name}!' if __name__ == '__main__': fire.Fire() @@ -52,7 +52,7 @@ command line. import fire def hello(name): - return 'Hello {name}!'.format(name=name) + return f'Hello {name}!' if __name__ == '__main__': fire.Fire(hello) @@ -76,7 +76,7 @@ We can alternatively write this program like this: import fire def hello(name): - return 'Hello {name}!'.format(name=name) + return f'Hello {name}!' def main(): fire.Fire(hello) @@ -93,7 +93,7 @@ then simply this: import fire def hello(name): - return 'Hello {name}!'.format(name=name) + return f'Hello {name}!' def main(): fire.Fire(hello) @@ -105,7 +105,7 @@ If you have a file `example.py` that doesn't even import fire: ```python def hello(name): - return 'Hello {name}!'.format(name=name) + return f'Hello {name}!' ``` Then you can use it with Fire like this: @@ -589,6 +589,25 @@ default values that you don't want to specify. It is also important to remember to change the separator if you want to pass `-` as an argument. +##### Async Functions + +Fire supports calling async functions too. Here's a simple example. + +```python +import asyncio + +async def count_to_ten(): + for i in range(1, 11): + await asyncio.sleep(1) + print(i) + +if __name__ == '__main__': + fire.Fire(count_to_ten) +``` + +Whenever fire encounters a coroutine function, it runs it, blocking until it completes. + + ### Argument Parsing The types of the arguments are determined by their values, rather than by the diff --git a/examples/widget/widget.py b/examples/widget/widget.py index bf1cbeb2..9092ad75 100644 --- a/examples/widget/widget.py +++ b/examples/widget/widget.py @@ -25,7 +25,7 @@ def whack(self, n=1): def bang(self, noise='bang'): """Makes a loud noise.""" - return '{noise} bang!'.format(noise=noise) + return f'{noise} bang!' def main(): diff --git a/fire/__init__.py b/fire/__init__.py index fae18489..742b03ac 100644 --- a/fire/__init__.py +++ b/fire/__init__.py @@ -14,10 +14,6 @@ """The Python Fire module.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - from fire.core import Fire __all__ = ['Fire'] diff --git a/fire/__main__.py b/fire/__main__.py index 9d8227ad..11fb1b42 100644 --- a/fire/__main__.py +++ b/fire/__main__.py @@ -18,11 +18,8 @@ This allows using Fire with third-party libraries without modifying their code. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import importlib +from importlib import util import os import sys @@ -61,27 +58,13 @@ def import_from_file_path(path): module_name = os.path.basename(path) - if sys.version_info.major == 3 and sys.version_info.minor < 5: - loader = importlib.machinery.SourceFileLoader( # pylint: disable=no-member - fullname=module_name, - path=path, - ) - - module = loader.load_module(module_name) # pylint: disable=deprecated-method - - elif sys.version_info.major == 3: - from importlib import util # pylint: disable=g-import-not-at-top,import-outside-toplevel,no-name-in-module - spec = util.spec_from_file_location(module_name, path) - - if spec is None: - raise IOError('Unable to load module from specified path.') + spec = util.spec_from_file_location(module_name, path) - module = util.module_from_spec(spec) # pylint: disable=no-member - spec.loader.exec_module(module) # pytype: disable=attribute-error + if spec is None: + raise IOError('Unable to load module from specified path.') - else: - import imp # pylint: disable=g-import-not-at-top,import-outside-toplevel,deprecated-module,import-error - module = imp.load_source(module_name, path) + module = util.module_from_spec(spec) # pylint: disable=no-member + spec.loader.exec_module(module) # pytype: disable=attribute-error return module, module_name diff --git a/fire/completion.py b/fire/completion.py index 4393880d..625e9d86 100644 --- a/fire/completion.py +++ b/fire/completion.py @@ -23,7 +23,6 @@ import inspect from fire import inspectutils -import six def Script(name, component, default_options=None, shell='bash'): @@ -278,10 +277,7 @@ def _FishScript(name, commands, default_options=None): ) return fish_source.format( - global_options=' '.join( - '"{option}"'.format(option=option) - for option in global_options - ) + global_options=' '.join(f'"{option}"' for option in global_options) ) @@ -308,7 +304,7 @@ def MemberVisible(component, name, member, class_attrs=None, verbose=False): Returns A boolean value indicating whether the member should be included. """ - if isinstance(name, six.string_types) and name.startswith('__'): + if isinstance(name, str) and name.startswith('__'): return False if verbose: return True @@ -316,10 +312,11 @@ def MemberVisible(component, name, member, class_attrs=None, verbose=False): or member is division or member is print_function): return False - if isinstance(member, type(absolute_import)) and six.PY34: + if isinstance(member, type(absolute_import)): return False - if inspect.ismodule(member) and member is six: - # TODO(dbieber): Determine more generally which modules to hide. + # TODO(dbieber): Determine more generally which modules to hide. + modules_to_hide = [] + if inspect.ismodule(member) and member in modules_to_hide: return False if inspect.isclass(component): # If class_attrs has not been provided, compute it. @@ -336,14 +333,7 @@ def MemberVisible(component, name, member, class_attrs=None, verbose=False): tuplegetter = getattr(collections, '_tuplegetter', type(None)) if isinstance(class_attr.object, tuplegetter): return False - if (six.PY2 and inspect.isfunction(component) - and name in ('func_closure', 'func_code', 'func_defaults', - 'func_dict', 'func_doc', 'func_globals', 'func_name')): - return False - if (six.PY2 and inspect.ismethod(component) - and name in ('im_class', 'im_func', 'im_self')): - return False - if isinstance(name, six.string_types): + if isinstance(name, str): return not name.startswith('_') return True # Default to including the member @@ -392,7 +382,7 @@ def _CompletionsFromArgs(fn_args): completions = [] for arg in fn_args: arg = arg.replace('_', '-') - completions.append('--{arg}'.format(arg=arg)) + completions.append(f'--{arg}') return completions @@ -438,7 +428,7 @@ def _FormatForCommand(token): Returns: The transformed token. """ - if not isinstance(token, six.string_types): + if not isinstance(token, str): token = str(token) if token.startswith('_'): diff --git a/fire/completion_test.py b/fire/completion_test.py index 582e5bbc..c0d5d24f 100644 --- a/fire/completion_test.py +++ b/fire/completion_test.py @@ -14,10 +14,6 @@ """Tests for the completion module.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - from fire import completion from fire import test_components as tc from fire import testutils @@ -37,9 +33,8 @@ def testCompletionBashScript(self): self.assertIn('command', script) self.assertIn('halt', script) - assert_template = '{command})' for last_command in ['command', 'halt']: - self.assertIn(assert_template.format(command=last_command), script) + self.assertIn(f'{last_command})', script) def testCompletionFishScript(self): # A sanity check test to make sure the fish completion script satisfies diff --git a/fire/console/console_attr.py b/fire/console/console_attr.py index 815e16b8..c0a3d784 100644 --- a/fire/console/console_attr.py +++ b/fire/console/console_attr.py @@ -100,8 +100,6 @@ from fire.console import encoding as encoding_util from fire.console import text -import six - # TODO: Unify this logic with console.style.mappings class BoxLineCharacters(object): @@ -355,9 +353,9 @@ def ConvertOutputToUnicode(self, buf): Returns: The console output string buf converted to unicode. """ - if isinstance(buf, six.text_type): + if isinstance(buf, str): buf = buf.encode(self._encoding) - return six.text_type(buf, self._encoding, 'replace') + return str(buf, self._encoding, 'replace') def GetBoxLineCharacters(self): """Returns the box/line drawing characters object. @@ -480,7 +478,7 @@ def DisplayWidth(self, buf): Returns: The display width of buf, handling unicode and ANSI controls. """ - if not isinstance(buf, six.string_types): + if not isinstance(buf, str): # Handle non-string objects like Colorizer(). return len(buf) @@ -595,16 +593,16 @@ def __init__(self, string, color, justify=None): self._justify = justify def __eq__(self, other): - return self._string == six.text_type(other) + return self._string == str(other) def __ne__(self, other): return not self == other def __gt__(self, other): - return self._string > six.text_type(other) + return self._string > str(other) def __lt__(self, other): - return self._string < six.text_type(other) + return self._string < str(other) def __ge__(self, other): return not self < other @@ -692,7 +690,7 @@ def GetCharacterDisplayWidth(char): Returns: The monospaced terminal display width of char: either 0, 1, or 2. """ - if not isinstance(char, six.text_type): + if not isinstance(char, str): # Non-unicode chars have width 1. Don't use this function on control chars. return 1 @@ -779,7 +777,7 @@ def EncodeToBytes(data): return data # Coerce to text that will be converted to bytes. - s = six.text_type(data) + s = str(data) try: # Assume the text can be directly converted to bytes (8-bit ascii). diff --git a/fire/console/console_io.py b/fire/console/console_io.py index 3d3b9f81..ec0858d9 100644 --- a/fire/console/console_io.py +++ b/fire/console/console_io.py @@ -15,10 +15,6 @@ """General console printing utilities used by the Cloud SDK.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import os import signal import subprocess diff --git a/fire/console/encoding.py b/fire/console/encoding.py index 41bda634..0a7fedfc 100644 --- a/fire/console/encoding.py +++ b/fire/console/encoding.py @@ -22,8 +22,6 @@ import sys -import six - def Encode(string, encoding=None): """Encode the text string to a byte string. @@ -35,18 +33,7 @@ def Encode(string, encoding=None): Returns: str, The binary string. """ - if string is None: - return None - if not six.PY2: - # In Python 3, the environment sets and gets accept and return text strings - # only, and it handles the encoding itself so this is not necessary. - return string - if isinstance(string, six.binary_type): - # Already an encoded byte string, we are done - return string - - encoding = encoding or _GetEncoding() - return string.encode(encoding) + return string def Decode(data, encoding=None): @@ -67,20 +54,13 @@ def Decode(data, encoding=None): return None # First we are going to get the data object to be a text string. - # Don't use six.string_types here because on Python 3 bytes is not considered - # a string type and we want to include that. - if isinstance(data, six.text_type) or isinstance(data, six.binary_type): + if isinstance(data, str) or isinstance(data, bytes): string = data else: # Some non-string type of object. - try: - string = six.text_type(data) - except (TypeError, UnicodeError): - # The string cannot be converted to unicode -- default to str() which will - # catch objects with special __str__ methods. - string = str(data) + string = str(data) - if isinstance(string, six.text_type): + if isinstance(string, str): # Our work is done here. return string @@ -199,7 +179,8 @@ def EncodeEnv(env, encoding=None): encoding = encoding or _GetEncoding() return { Encode(k, encoding=encoding): Encode(v, encoding=encoding) - for k, v in six.iteritems(env)} + for k, v in env.items() + } def _GetEncoding(): diff --git a/fire/console/files.py b/fire/console/files.py index 69970f43..97222c3d 100644 --- a/fire/console/files.py +++ b/fire/console/files.py @@ -24,8 +24,6 @@ from fire.console import encoding as encoding_util from fire.console import platforms -import six - def _GetSystemPath(): """Returns properly encoded system PATH variable string.""" @@ -48,7 +46,7 @@ def _FindExecutableOnPath(executable, path, pathext): ValueError: invalid input. """ - if isinstance(pathext, six.string_types): + if isinstance(pathext, str): raise ValueError('_FindExecutableOnPath(..., pathext=\'{0}\') failed ' 'because pathext must be an iterable of strings, but got ' 'a string.'.format(pathext)) diff --git a/fire/console/platforms.py b/fire/console/platforms.py index 018eb89e..13fd8204 100644 --- a/fire/console/platforms.py +++ b/fire/console/platforms.py @@ -153,6 +153,8 @@ def Current(): return OperatingSystem.MACOSX elif 'cygwin' in sys.platform: return OperatingSystem.CYGWIN + elif 'msys' in sys.platform: + return OperatingSystem.MSYS return None @staticmethod diff --git a/fire/core.py b/fire/core.py index 6367262d..6cd1907e 100644 --- a/fire/core.py +++ b/fire/core.py @@ -49,14 +49,10 @@ def main(argv): --trace: Get the Fire Trace for the command. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +import asyncio import inspect import json import os -import pipes import re import shlex import sys @@ -72,10 +68,6 @@ def main(argv): from fire import trace from fire import value_types from fire.console import console_io -import six - -if six.PY34: - import asyncio # pylint: disable=import-error,g-import-not-at-top # pytype: disable=import-error def Fire(component=None, command=None, name=None, serialize=None): @@ -114,7 +106,7 @@ def Fire(component=None, command=None, name=None, serialize=None): name = name or os.path.basename(sys.argv[0]) # Get args as a list. - if isinstance(command, six.string_types): + if isinstance(command, str): args = shlex.split(command) elif isinstance(command, (list, tuple)): args = command @@ -146,7 +138,7 @@ def Fire(component=None, command=None, name=None, serialize=None): _DisplayError(component_trace) raise FireExit(2, component_trace) if component_trace.show_trace and component_trace.show_help: - output = ['Fire trace:\n{trace}\n'.format(trace=component_trace)] + output = [f'Fire trace:\n{component_trace}\n'] result = component_trace.GetResult() help_text = helptext.HelpText( result, trace=component_trace, verbose=component_trace.verbose) @@ -154,7 +146,7 @@ def Fire(component=None, command=None, name=None, serialize=None): Display(output, out=sys.stderr) raise FireExit(0, component_trace) if component_trace.show_trace: - output = ['Fire trace:\n{trace}'.format(trace=component_trace)] + output = [f'Fire trace:\n{component_trace}'] Display(output, out=sys.stderr) raise FireExit(0, component_trace) if component_trace.show_help: @@ -238,9 +230,9 @@ def _IsHelpShortcut(component_trace, remaining_args): if show_help: component_trace.show_help = True - command = '{cmd} -- --help'.format(cmd=component_trace.GetCommand()) - print('INFO: Showing help with the command {cmd}.\n'.format( - cmd=pipes.quote(command)), file=sys.stderr) + command = f'{component_trace.GetCommand()} -- --help' + print(f'INFO: Showing help with the command {shlex.quote(command)}.\n', + file=sys.stderr) return show_help @@ -294,9 +286,9 @@ def _DisplayError(component_trace): show_help = True if show_help: - command = '{cmd} -- --help'.format(cmd=component_trace.GetCommand()) - print('INFO: Showing help with the command {cmd}.\n'.format( - cmd=pipes.quote(command)), file=sys.stderr) + command = f'{component_trace.GetCommand()} -- --help' + print(f'INFO: Showing help with the command {shlex.quote(command)}.\n', + file=sys.stderr) help_text = helptext.HelpText(result, trace=component_trace, verbose=component_trace.verbose) output.append(help_text) @@ -334,14 +326,13 @@ def _DictAsString(result, verbose=False): return '{}' longest_key = max(len(str(key)) for key in result_visible.keys()) - format_string = '{{key:{padding}s}} {{value}}'.format(padding=longest_key + 1) + format_string = f'{{key:{longest_key + 1}s}} {{value}}' lines = [] for key, value in result.items(): if completion.MemberVisible(result, key, value, class_attrs=class_attrs, verbose=verbose): - line = format_string.format(key=str(key) + ':', - value=_OneLineResult(value)) + line = format_string.format(key=f'{key}:', value=_OneLineResult(value)) lines.append(line) return '\n'.join(lines) @@ -349,16 +340,16 @@ def _DictAsString(result, verbose=False): def _OneLineResult(result): """Returns result serialized to a single line string.""" # TODO(dbieber): Ensure line is fewer than eg 120 characters. - if isinstance(result, six.string_types): + if isinstance(result, str): return str(result).replace('\n', ' ') # TODO(dbieber): Show a small amount of usage information about the function # or module if it fits cleanly on the line. if inspect.isfunction(result): - return ''.format(name=result.__name__) + return f'' if inspect.ismodule(result): - return ''.format(name=result.__name__) + return f'' try: # Don't force conversion to ascii. @@ -879,6 +870,7 @@ def _ParseKeywordArgs(args, fn_spec): key, value = stripped_argument.split('=', 1) else: key = stripped_argument + value = None # value will be set later on. key = key.replace('-', '_') is_bool_syntax = (not contains_equals and @@ -896,9 +888,10 @@ def _ParseKeywordArgs(args, fn_spec): if len(matching_fn_args) == 1: keyword = matching_fn_args[0] elif len(matching_fn_args) > 1: - raise FireError("The argument '{}' is ambiguous as it could " - "refer to any of the following arguments: {}".format( - argument, matching_fn_args)) + raise FireError( + f"The argument '{argument}' is ambiguous as it could " + f"refer to any of the following arguments: {matching_fn_args}" + ) # Determine the value. if not keyword: diff --git a/fire/core_test.py b/fire/core_test.py index 75b76998..90b7f466 100644 --- a/fire/core_test.py +++ b/fire/core_test.py @@ -14,17 +14,12 @@ """Tests for the core module.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function +from unittest import mock from fire import core from fire import test_components as tc from fire import testutils from fire import trace -import mock - -import six class CoreTest(testutils.BaseTestCase): @@ -218,13 +213,11 @@ def serialize(x): with self.assertRaises(core.FireError): core.Fire(ident, command=['asdf'], serialize=55) - @testutils.skipIf(six.PY2, 'lru_cache is Python 3 only.') def testLruCacheDecoratorBoundArg(self): self.assertEqual( core.Fire(tc.py3.LruCacheDecoratedMethod, # pytype: disable=module-attr command=['lru_cache_in_class', 'foo']), 'foo') - @testutils.skipIf(six.PY2, 'lru_cache is Python 3 only.') def testLruCacheDecorator(self): self.assertEqual( core.Fire(tc.py3.lru_cache_decorated, # pytype: disable=module-attr diff --git a/fire/custom_descriptions.py b/fire/custom_descriptions.py index 266671f1..768f0e23 100644 --- a/fire/custom_descriptions.py +++ b/fire/custom_descriptions.py @@ -36,12 +36,7 @@ descriptions for primitive typed values. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - from fire import formatting -import six TWO_DOUBLE_QUOTES = '""' STRING_DESC_PREFIX = 'The string ' @@ -64,13 +59,11 @@ def NeedsCustomDescription(component): Whether the component should use a custom description and summary. """ type_ = type(component) - if (type_ in six.string_types - or type_ in six.integer_types - or type_ is six.text_type - or type_ is six.binary_type + if ( + type_ in (str, int, bytes) or type_ in (float, complex, bool) or type_ in (dict, tuple, list, set, frozenset) - ): + ): return True return False diff --git a/fire/custom_descriptions_test.py b/fire/custom_descriptions_test.py index 79d7c7a1..6cff2d5d 100644 --- a/fire/custom_descriptions_test.py +++ b/fire/custom_descriptions_test.py @@ -14,10 +14,6 @@ """Tests for custom description module.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - from fire import custom_descriptions from fire import testutils diff --git a/fire/decorators.py b/fire/decorators.py index b2e9b322..eb5b0d20 100644 --- a/fire/decorators.py +++ b/fire/decorators.py @@ -18,10 +18,6 @@ command line arguments to client code. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import inspect FIRE_METADATA = 'FIRE_METADATA' diff --git a/fire/decorators_test.py b/fire/decorators_test.py index cc7d6203..a316b79f 100644 --- a/fire/decorators_test.py +++ b/fire/decorators_test.py @@ -14,10 +14,6 @@ """Tests for the decorators module.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - from fire import core from fire import decorators from fire import testutils diff --git a/fire/docstrings.py b/fire/docstrings.py index 1cfadea9..2d7c7e63 100644 --- a/fire/docstrings.py +++ b/fire/docstrings.py @@ -49,10 +49,6 @@ - "True | False" indicates bool type. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import collections import enum import re diff --git a/fire/docstrings_fuzz_test.py b/fire/docstrings_fuzz_test.py index 7609f4f8..66be8006 100644 --- a/fire/docstrings_fuzz_test.py +++ b/fire/docstrings_fuzz_test.py @@ -14,10 +14,6 @@ """Fuzz tests for the docstring parser module.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - from fire import docstrings from fire import testutils diff --git a/fire/docstrings_test.py b/fire/docstrings_test.py index 0d6e5d18..ce516944 100644 --- a/fire/docstrings_test.py +++ b/fire/docstrings_test.py @@ -14,10 +14,6 @@ """Tests for fire docstrings module.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - from fire import docstrings from fire import testutils diff --git a/fire/fire_import_test.py b/fire/fire_import_test.py index c5975681..a6b4acc3 100644 --- a/fire/fire_import_test.py +++ b/fire/fire_import_test.py @@ -15,10 +15,10 @@ """Tests importing the fire module.""" import sys +from unittest import mock import fire from fire import testutils -import mock class FireImportTest(testutils.BaseTestCase): diff --git a/fire/fire_test.py b/fire/fire_test.py index 8b904c29..99b4a7c6 100644 --- a/fire/fire_test.py +++ b/fire/fire_test.py @@ -14,20 +14,14 @@ """Tests for the fire module.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import os import sys +from unittest import mock import fire from fire import test_components as tc from fire import testutils -import mock -import six - class FireTest(testutils.BaseTestCase): @@ -184,7 +178,6 @@ def testFireAnnotatedArgs(self): self.assertEqual(fire.Fire(tc.Annotations, command=['double', '5']), 10) self.assertEqual(fire.Fire(tc.Annotations, command=['triple', '5']), 15) - @testutils.skipIf(six.PY2, 'Keyword-only arguments not in Python 2.') def testFireKeywordOnlyArgs(self): with self.assertRaisesFireExit(2): # Keyword arguments must be passed with flag syntax. @@ -712,8 +705,6 @@ def testClassWithInvalidProperty(self): fire.Fire(tc.InvalidProperty, command=['double', '10']), 20 ) - @testutils.skipIf(sys.version_info[0:2] <= (3, 4), - 'Cannot inspect wrapped signatures in Python 2 or 3.4.') def testHelpKwargsDecorator(self): # Issue #190, follow the wrapped method instead of crashing. with self.assertRaisesFireExit(0): @@ -721,7 +712,6 @@ def testHelpKwargsDecorator(self): with self.assertRaisesFireExit(0): fire.Fire(tc.decorated_method, command=['--help']) - @testutils.skipIf(six.PY2, 'Asyncio not available in Python 2.') def testFireAsyncio(self): self.assertEqual(fire.Fire(tc.py3.WithAsyncio, command=['double', '--count', '10']), 20) diff --git a/fire/formatting.py b/fire/formatting.py index faef8047..68484c27 100644 --- a/fire/formatting.py +++ b/fire/formatting.py @@ -14,10 +14,6 @@ """Formatting utilities for use in creating help text.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - from fire import formatting_windows # pylint: disable=unused-import import termcolor diff --git a/fire/formatting_test.py b/fire/formatting_test.py index 05a88c49..e0f6699d 100644 --- a/fire/formatting_test.py +++ b/fire/formatting_test.py @@ -14,10 +14,6 @@ """Tests for formatting.py.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - from fire import formatting from fire import testutils diff --git a/fire/formatting_windows.py b/fire/formatting_windows.py index ce0f677d..f8241eaa 100644 --- a/fire/formatting_windows.py +++ b/fire/formatting_windows.py @@ -14,10 +14,6 @@ """This module is used for enabling formatting on Windows.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import ctypes import os import platform diff --git a/fire/helptext.py b/fire/helptext.py index 6e7fbb07..e57eb7d8 100644 --- a/fire/helptext.py +++ b/fire/helptext.py @@ -29,13 +29,8 @@ information. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import collections import itertools -import sys from fire import completion from fire import custom_descriptions @@ -111,7 +106,7 @@ def _NameSection(component, info, trace=None, verbose=False): LINE_LENGTH) if summary: - text = current_command + ' - ' + summary + text = f'{current_command} - {summary}' else: text = current_command return ('NAME', text) @@ -137,11 +132,7 @@ def _SynopsisSection(component, actions_grouped_by_kind, spec, metadata, continuations.append(trace.separator) continuation = ' | '.join(continuations) - synopsis_template = '{current_command} {continuation}' - text = synopsis_template.format( - current_command=current_command, - continuation=continuation) - + text = f'{current_command} {continuation}' return ('SYNOPSIS', text) @@ -248,8 +239,6 @@ def _ArgsAndFlagsSections(info, spec, metadata): if spec.varkw: # Include kwargs documented via :key param: documented_kwargs = [] - flag_string = '--{name}' - short_flag_string = '-{short_name}, --{name}' # add short flags if possible flags = docstring_info.args or [] @@ -258,11 +247,10 @@ def _ArgsAndFlagsSections(info, spec, metadata): for flag in flags: if isinstance(flag, docstrings.KwargInfo): if flag.name[0] in unique_short_flags: - flag_string = short_flag_string.format( - name=flag.name, short_name=flag.name[0] - ) + short_name = flag.name[0] + flag_string = f'-{short_name}, --{flag.name}' else: - flag_string = flag_string.format(name=flag.name) + flag_string = f'--{flag.name}' flag_item = _CreateFlagItem( flag.name, docstring_info, spec, @@ -352,9 +340,9 @@ def _GetArgsAndFlagsString(spec, metadata): for arg in args_with_no_defaults] else: arg_strings = [ - '--{arg}={arg_upper}'.format( - arg=arg, arg_upper=formatting.Underline(arg.upper())) - for arg in args_with_no_defaults] + f'--{arg}={formatting.Underline(arg.upper())}' + for arg in args_with_no_defaults + ] arg_and_flag_strings.extend(arg_strings) # If there are any arguments that are treated as flags: @@ -362,8 +350,8 @@ def _GetArgsAndFlagsString(spec, metadata): arg_and_flag_strings.append('') if spec.varargs: - varargs_string = '[{varargs}]...'.format( - varargs=formatting.Underline(spec.varargs.upper())) + varargs_underlined = formatting.Underline(spec.varargs.upper()) + varargs_string = f'[{varargs_underlined}]...' arg_and_flag_strings.append(varargs_string) return ' '.join(arg_and_flag_strings) @@ -406,7 +394,7 @@ def _GetActionsGroupedByKind(component, verbose=False): if component_len < 10: indexes.Add(name=', '.join(str(x) for x in range(component_len))) else: - indexes.Add(name='0..{max}'.format(max=component_len-1)) + indexes.Add(name=f'0..{component_len-1}') return [groups, commands, values, indexes] @@ -421,10 +409,8 @@ def _GetCurrentCommand(trace=None, include_separators=True): def _CreateOutputSection(name, content): - return """{name} -{content}""".format( - name=formatting.Bold(name), - content=formatting.Indent(content, SECTION_INDENTATION)) + return f"""{formatting.Bold(name)} +{formatting.Indent(content, SECTION_INDENTATION)}""" def _CreateArgItem(arg, docstring_info, spec): @@ -435,7 +421,7 @@ def _CreateArgItem(arg, docstring_info, spec): docstring_info: A docstrings.DocstringInfo namedtuple with information about the containing function's docstring. spec: An instance of fire.inspectutils.FullArgSpec, containing type and - default information about the arguments to a callable. + default information about the arguments to a callable. Returns: A string to be used in constructing the help screen for the function. @@ -450,7 +436,7 @@ def _CreateArgItem(arg, docstring_info, spec): arg_string = formatting.BoldUnderline(arg.upper()) arg_type = _GetArgType(arg, spec) - arg_type = 'Type: {}'.format(arg_type) if arg_type else '' + arg_type = f'Type: {arg_type}' if arg_type else '' available_space = max_str_length - len(arg_type) arg_type = ( formatting.EllipsisTruncate(arg_type, available_space, max_str_length)) @@ -489,14 +475,13 @@ def _CreateFlagItem(flag, docstring_info, spec, required=False, description = _GetArgDescription(flag, docstring_info) if not flag_string: - flag_string_template = '--{flag_name}={flag_name_upper}' - flag_string = flag_string_template.format( - flag_name=flag, - flag_name_upper=formatting.Underline(flag.upper())) + flag_name_upper=formatting.Underline(flag.upper()) + flag_string = f'--{flag}={flag_name_upper}' if required: flag_string += ' (required)' if short_arg: - flag_string = '-{short_flag}, '.format(short_flag=flag[0]) + flag_string + short_flag = flag[0] + flag_string = f'-{short_flag}, {flag_string}' arg_type = _GetArgType(flag, spec) arg_default = _GetArgDefault(flag, spec) @@ -504,14 +489,14 @@ def _CreateFlagItem(flag, docstring_info, spec, required=False, # We need to handle the case where there is a default of None, but otherwise # the argument has another type. if arg_default == 'None': - arg_type = 'Optional[{}]'.format(arg_type) + arg_type = f'Optional[{arg_type}]' - arg_type = 'Type: {}'.format(arg_type) if arg_type else '' + arg_type = f'Type: {arg_type}' if arg_type else '' available_space = max_str_length - len(arg_type) arg_type = ( formatting.EllipsisTruncate(arg_type, available_space, max_str_length)) - arg_default = 'Default: {}'.format(arg_default) if arg_default else '' + arg_default = f'Default: {arg_default}' if arg_default else '' available_space = max_str_length - len(arg_default) arg_default = ( formatting.EllipsisTruncate(arg_default, available_space, max_str_length)) @@ -537,9 +522,7 @@ def _GetArgType(arg, spec): if arg in spec.annotations: arg_type = spec.annotations[arg] try: - if sys.version_info[0:2] >= (3, 3): - return arg_type.__qualname__ - return arg_type.__name__ + return arg_type.__qualname__ except AttributeError: # Some typing objects, such as typing.Union do not have either a __name__ # or __qualname__ attribute. @@ -574,15 +557,15 @@ def _GetArgDefault(flag, spec): def _CreateItem(name, description, indent=2): if not description: return name - return """{name} -{description}""".format(name=name, - description=formatting.Indent(description, indent)) + description = formatting.Indent(description, indent) + return f"""{name} +{description}""" def _GetArgDescription(name, docstring_info): if docstring_info.args: for arg_in_docstring in docstring_info.args: - if arg_in_docstring.name in (name, '*' + name, '**' + name): + if arg_in_docstring.name in (name, f'*{name}', f'**{name}'): return arg_in_docstring.description return None @@ -628,9 +611,9 @@ def _ValuesUsageDetailsSection(component, values): def _NewChoicesSection(name, choices): + name_formatted = formatting.Bold(formatting.Underline(name)) return _CreateItem( - '{name} is one of the following:'.format( - name=formatting.Bold(formatting.Underline(name))), + f'{name_formatted} is one of the following:', '\n' + '\n\n'.join(choices), indent=1) @@ -646,11 +629,6 @@ def UsageText(component, trace=None, verbose=False): Returns: String suitable for display in an error screen. """ - output_template = """Usage: {continued_command} -{availability_lines} -For detailed information on this command, run: - {help_command}""" - # Get the command so far: if trace: command = trace.GetCommand() @@ -694,15 +672,16 @@ def UsageText(component, trace=None, verbose=False): + '--help' ) - return output_template.format( - continued_command=continued_command, - availability_lines=''.join(availability_lines), - help_command=help_command) + return f"""Usage: {continued_command} +{''.join(availability_lines)} +For detailed information on this command, run: + {help_command}""" def _GetPossibleActionsUsageString(possible_actions): if possible_actions: - return '<{actions}>'.format(actions='|'.join(possible_actions)) + actions_str = '|'.join(possible_actions) + return f'<{actions_str}>' return None @@ -711,7 +690,7 @@ def _UsageAvailabilityLines(actions_grouped_by_kind): for action_group in actions_grouped_by_kind: if action_group.members: availability_line = _CreateAvailabilityLine( - header='available {plural}:'.format(plural=action_group.plural), + header=f'available {action_group.plural}:', items=action_group.names ) availability_lines.append(availability_line) @@ -727,7 +706,7 @@ def _GetCallableUsageItems(spec, metadata): accepts_positional_args = metadata.get(decorators.ACCEPTS_POSITIONAL_ARGS) if not accepts_positional_args: - items = ['--{arg}={upper}'.format(arg=arg, upper=arg.upper()) + items = [f'--{arg}={arg.upper()}' for arg in args_with_no_defaults] else: items = [arg.upper() for arg in args_with_no_defaults] @@ -737,7 +716,7 @@ def _GetCallableUsageItems(spec, metadata): items.append('') if spec.varargs: - items.append('[{varargs}]...'.format(varargs=spec.varargs.upper())) + items.append(f'[{spec.varargs.upper()}]...') return items @@ -752,10 +731,10 @@ def _GetCallableAvailabilityLines(spec): args_with_defaults = spec.args[len(spec.args) - len(spec.defaults):] # TODO(dbieber): Handle args_with_no_defaults if not accepts_positional_args. - optional_flags = [('--' + flag) for flag in itertools.chain( + optional_flags = [f'--{flag}' for flag in itertools.chain( args_with_defaults, _KeywordOnlyArguments(spec, required=False))] required_flags = [ - ('--' + flag) for flag in _KeywordOnlyArguments(spec, required=True) + f'--{flag}' for flag in _KeywordOnlyArguments(spec, required=True) ] # Flags section: diff --git a/fire/helptext_test.py b/fire/helptext_test.py index 404d9812..4d35dc0a 100644 --- a/fire/helptext_test.py +++ b/fire/helptext_test.py @@ -14,12 +14,7 @@ """Tests for the helptext module.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import os -import sys import textwrap from fire import formatting @@ -27,7 +22,6 @@ from fire import test_components as tc from fire import testutils from fire import trace -import six class HelpTest(testutils.BaseTestCase): @@ -129,9 +123,6 @@ def testHelpTextFunctionWithKwargsAndDefaults(self): 'Additional undocumented flags may also be accepted.', help_screen) - @testutils.skipIf( - sys.version_info[0:2] < (3, 5), - 'Python < 3.5 does not support type hints.') def testHelpTextFunctionWithDefaultsAndTypes(self): component = ( tc.py3.WithDefaultsAndTypes().double) # pytype: disable=module-attr @@ -146,9 +137,6 @@ def testHelpTextFunctionWithDefaultsAndTypes(self): help_screen) self.assertNotIn('NOTES', help_screen) - @testutils.skipIf( - sys.version_info[0:2] < (3, 5), - 'Python < 3.5 does not support type hints.') def testHelpTextFunctionWithTypesAndDefaultNone(self): component = ( tc.py3.WithDefaultsAndTypes().get_int) # pytype: disable=module-attr @@ -164,9 +152,6 @@ def testHelpTextFunctionWithTypesAndDefaultNone(self): help_screen) self.assertNotIn('NOTES', help_screen) - @testutils.skipIf( - sys.version_info[0:2] < (3, 5), - 'Python < 3.5 does not support type hints.') def testHelpTextFunctionWithTypes(self): component = tc.py3.WithTypes().double # pytype: disable=module-attr help_screen = helptext.HelpText( @@ -182,9 +167,6 @@ def testHelpTextFunctionWithTypes(self): 'NOTES\n You can also use flags syntax for POSITIONAL ARGUMENTS', help_screen) - @testutils.skipIf( - sys.version_info[0:2] < (3, 5), - 'Python < 3.5 does not support type hints.') def testHelpTextFunctionWithLongTypes(self): component = tc.py3.WithTypes().long_type # pytype: disable=module-attr help_screen = helptext.HelpText( @@ -280,8 +262,6 @@ def testHelpTextNoInit(self): self.assertIn('NAME\n OldStyleEmpty', help_screen) self.assertIn('SYNOPSIS\n OldStyleEmpty', help_screen) - @testutils.skipIf( - six.PY2, 'Python 2 does not support keyword-only arguments.') def testHelpTextKeywordOnlyArgumentsWithDefault(self): component = tc.py3.KeywordOnly.with_default # pytype: disable=module-attr output = helptext.HelpText( @@ -289,8 +269,6 @@ def testHelpTextKeywordOnlyArgumentsWithDefault(self): self.assertIn('NAME\n with_default', output) self.assertIn('FLAGS\n -x, --x=X', output) - @testutils.skipIf( - six.PY2, 'Python 2 does not support keyword-only arguments.') def testHelpTextKeywordOnlyArgumentsWithoutDefault(self): component = tc.py3.KeywordOnly.double # pytype: disable=module-attr output = helptext.HelpText( @@ -298,9 +276,6 @@ def testHelpTextKeywordOnlyArgumentsWithoutDefault(self): self.assertIn('NAME\n double', output) self.assertIn('FLAGS\n -c, --count=COUNT (required)', output) - @testutils.skipIf( - six.PY2, - 'Python 2 does not support required name-only arguments.') def testHelpTextFunctionMixedDefaults(self): component = tc.py3.HelpTextComponent().identity t = trace.FireTrace(component, name='FunctionMixedDefaults') @@ -527,9 +502,6 @@ def testUsageOutputFunctionWithDocstring(self): textwrap.dedent(expected_output).lstrip('\n'), usage_output) - @testutils.skipIf( - six.PY2, - 'Python 2 does not support required name-only arguments.') def testUsageOutputFunctionMixedDefaults(self): component = tc.py3.HelpTextComponent().identity t = trace.FireTrace(component, name='FunctionMixedDefaults') diff --git a/fire/inspectutils.py b/fire/inspectutils.py index 15f32f91..a3ae7c27 100644 --- a/fire/inspectutils.py +++ b/fire/inspectutils.py @@ -14,21 +14,13 @@ """Inspection utility functions for Python Fire.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +import asyncio import inspect import sys import types from fire import docstrings -import six - -if six.PY34: - import asyncio # pylint: disable=import-error,g-import-not-at-top # pytype: disable=import-error - class FullArgSpec(object): """The arguments of a function, as in Python 3's inspect.FullArgSpec.""" @@ -78,8 +70,6 @@ class with an __init__ method. if inspect.isclass(fn): # If the function is a class, we try to use its init method. skip_arg = True - if six.PY2 and hasattr(fn, '__init__'): - fn = fn.__init__ elif inspect.ismethod(fn): # If the function is a bound method, we skip the `self` argument. skip_arg = fn.__self__ is not None @@ -95,16 +85,6 @@ class with an __init__ method. return fn, skip_arg -def Py2GetArgSpec(fn): - """A wrapper around getargspec that tries both fn and fn.__call__.""" - try: - return inspect.getargspec(fn) # pylint: disable=deprecated-method,no-member - except TypeError: - if hasattr(fn, '__call__'): - return inspect.getargspec(fn.__call__) # pylint: disable=deprecated-method,no-member - raise - - def Py3GetFullArgSpec(fn): """A alternative to the builtin getfullargspec. @@ -189,13 +169,9 @@ def GetFullArgSpec(fn): if sys.version_info[0:2] >= (3, 5): (args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations) = Py3GetFullArgSpec(fn) - elif six.PY3: # Specifically Python 3.4. + else: # Specifically Python 3.4. (args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations) = inspect.getfullargspec(fn) # pylint: disable=deprecated-method,no-member - else: # six.PY2 - args, varargs, varkw, defaults = Py2GetArgSpec(fn) - kwonlyargs = kwonlydefaults = None - annotations = getattr(fn, '__annotations__', None) except TypeError: # If we can't get the argspec, how do we know if the fn should take args? @@ -225,7 +201,7 @@ def GetFullArgSpec(fn): return FullArgSpec() # In Python 3.5+ Py3GetFullArgSpec uses skip_bound_arg=True already. - skip_arg_required = six.PY2 or sys.version_info[0:2] == (3, 4) + skip_arg_required = sys.version_info[0:2] == (3, 4) if skip_arg_required and skip_arg and args: args.pop(0) # Remove 'self' or 'cls' from the list of arguments. return FullArgSpec(args, varargs, varkw, defaults, @@ -367,6 +343,6 @@ def GetClassAttrsDict(component): def IsCoroutineFunction(fn): try: - return six.PY34 and asyncio.iscoroutinefunction(fn) + return asyncio.iscoroutinefunction(fn) except: # pylint: disable=bare-except return False diff --git a/fire/inspectutils_test.py b/fire/inspectutils_test.py index ea8eb0e2..47de7e72 100644 --- a/fire/inspectutils_test.py +++ b/fire/inspectutils_test.py @@ -14,19 +14,12 @@ """Tests for the inspectutils module.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import os -import unittest from fire import inspectutils from fire import test_components as tc from fire import testutils -import six - class InspectUtilsTest(testutils.BaseTestCase): @@ -40,7 +33,6 @@ def testGetFullArgSpec(self): self.assertEqual(spec.kwonlydefaults, {}) self.assertEqual(spec.annotations, {'arg2': int, 'arg4': int}) - @unittest.skipIf(six.PY2, 'No keyword arguments in python 2') def testGetFullArgSpecPy3(self): spec = inspectutils.GetFullArgSpec(tc.py3.identity) self.assertEqual(spec.args, ['arg1', 'arg2', 'arg3', 'arg4']) @@ -125,10 +117,7 @@ def testInfoClass(self): def testInfoClassNoInit(self): info = inspectutils.Info(tc.OldStyleEmpty) - if six.PY2: - self.assertEqual(info.get('type_name'), 'classobj') - else: - self.assertEqual(info.get('type_name'), 'type') + self.assertEqual(info.get('type_name'), 'type') self.assertIn(os.path.join('fire', 'test_components.py'), info.get('file')) self.assertGreater(info.get('line'), 0) diff --git a/fire/interact.py b/fire/interact.py index 7df32841..eccd3990 100644 --- a/fire/interact.py +++ b/fire/interact.py @@ -20,10 +20,6 @@ InteractiveConsole class. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import inspect @@ -69,16 +65,17 @@ def _AvailableString(variables, verbose=False): lists = [ ('Modules', modules), ('Objects', other)] - liststrs = [] + list_strs = [] for name, varlist in lists: if varlist: - liststrs.append( - '{name}: {items}'.format(name=name, items=', '.join(sorted(varlist)))) + items_str = ', '.join(sorted(varlist)) + list_strs.append(f'{name}: {items_str}') + lists_str = '\n'.join(list_strs) return ( 'Fire is starting a Python REPL with the following objects:\n' - '{liststrs}\n' - ).format(liststrs='\n'.join(liststrs)) + f'{lists_str}\n' + ) def _EmbedIPython(variables, argv=None): diff --git a/fire/interact_test.py b/fire/interact_test.py index 29fa7597..2f286824 100644 --- a/fire/interact_test.py +++ b/fire/interact_test.py @@ -14,15 +14,11 @@ """Tests for the interact module.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function +from unittest import mock from fire import interact from fire import testutils -import mock - try: import IPython # pylint: disable=unused-import, g-import-not-at-top diff --git a/fire/parser.py b/fire/parser.py index 2aff8bd7..d945b8ce 100644 --- a/fire/parser.py +++ b/fire/parser.py @@ -14,12 +14,14 @@ """Provides parsing functionality used by Python Fire.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import argparse import ast +import sys + +if sys.version_info[0:2] < (3, 8): + _StrNode = ast.Str +else: + _StrNode = ast.Constant def CreateParser(): @@ -127,4 +129,4 @@ def _Replacement(node): # These are the only builtin constants supported by literal_eval. if value in ('True', 'False', 'None'): return node - return ast.Str(value) + return _StrNode(value) diff --git a/fire/parser_fuzz_test.py b/fire/parser_fuzz_test.py index af0be038..9739ec4e 100644 --- a/fire/parser_fuzz_test.py +++ b/fire/parser_fuzz_test.py @@ -14,10 +14,6 @@ """Fuzz tests for the parser module.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - from fire import parser from fire import testutils from hypothesis import example @@ -25,7 +21,6 @@ from hypothesis import settings from hypothesis import strategies as st import Levenshtein -import six class ParserFuzzTest(testutils.BaseTestCase): @@ -68,8 +63,8 @@ def testDefaultParseValueFuzz(self, value): raise try: - uvalue = six.text_type(value) - uresult = six.text_type(result) + uvalue = str(value) + uresult = str(result) except UnicodeDecodeError: # This is not what we're testing. return @@ -86,7 +81,7 @@ def testDefaultParseValueFuzz(self, value): if '#' in value: max_distance += len(value) - value.index('#') - if not isinstance(result, six.string_types): + if not isinstance(result, str): max_distance += value.count('0') # Leading 0s are stripped. # Note: We don't check distance for dicts since item order can be changed. diff --git a/fire/parser_test.py b/fire/parser_test.py index 8aeabc61..a404eea2 100644 --- a/fire/parser_test.py +++ b/fire/parser_test.py @@ -14,10 +14,6 @@ """Tests for the parser module.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - from fire import parser from fire import testutils diff --git a/fire/test_components.py b/fire/test_components.py index 5fcb056e..eb3a9e24 100644 --- a/fire/test_components.py +++ b/fire/test_components.py @@ -14,18 +14,11 @@ """This module has components that are used for testing Python Fire.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import collections import enum import functools -import six - -if six.PY3: - from fire import test_components_py3 as py3 # pylint: disable=unused-import,no-name-in-module,g-import-not-at-top +from fire import test_components_py3 as py3 # pylint: disable=unused-import,no-name-in-module,g-import-not-at-top def identity(arg1, arg2, arg3=10, arg4=20, *arg5, **arg6): # pylint: disable=keyword-arg-before-vararg @@ -395,8 +388,7 @@ def example_generator(n): [0, 1, 2, 3] """ - for i in range(n): - yield i + yield from range(n) def simple_set(): diff --git a/fire/test_components_bin.py b/fire/test_components_bin.py index fbb41952..62afdf11 100644 --- a/fire/test_components_bin.py +++ b/fire/test_components_bin.py @@ -17,10 +17,6 @@ This file is useful for replicating test results manually. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import fire from fire import test_components diff --git a/fire/test_components_test.py b/fire/test_components_test.py index f35d7ab5..531f882c 100644 --- a/fire/test_components_test.py +++ b/fire/test_components_test.py @@ -14,10 +14,6 @@ """Tests for the test_components module.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - from fire import test_components as tc from fire import testutils diff --git a/fire/testutils.py b/fire/testutils.py index 5f875147..eca37f43 100644 --- a/fire/testutils.py +++ b/fire/testutils.py @@ -14,22 +14,17 @@ """Utilities for Python Fire's tests.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import contextlib +import io import os import re import sys import unittest +from unittest import mock from fire import core from fire import trace -import mock -import six - class BaseTestCase(unittest.TestCase): """Shared test case for Python Fire tests.""" @@ -49,8 +44,8 @@ def assertOutputMatches(self, stdout='.*', stderr='.*', capture=True): Yields: Yields to the wrapped context. """ - stdout_fp = six.StringIO() - stderr_fp = six.StringIO() + stdout_fp = io.StringIO() + stderr_fp = io.StringIO() try: with mock.patch.object(sys, 'stdout', stdout_fp): with mock.patch.object(sys, 'stderr', stderr_fp): @@ -72,12 +67,6 @@ def assertOutputMatches(self, stdout='.*', stderr='.*', capture=True): raise AssertionError('%s: Expected %r to match %r' % (name, value, regexp)) - def assertRaisesRegex(self, *args, **kwargs): # pylint: disable=arguments-differ - if sys.version_info.major == 2: - return super(BaseTestCase, self).assertRaisesRegexp(*args, **kwargs) # pylint: disable=deprecated-method,no-member - else: - return super(BaseTestCase, self).assertRaisesRegex(*args, **kwargs) # pylint: disable=no-member - @contextlib.contextmanager def assertRaisesFireExit(self, code, regexp='.*'): """Asserts that a FireExit error is raised in the context. diff --git a/fire/testutils_test.py b/fire/testutils_test.py index ad604193..4cfc0937 100644 --- a/fire/testutils_test.py +++ b/fire/testutils_test.py @@ -14,16 +14,10 @@ """Test the test utilities for Fire's tests.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import sys from fire import testutils -import six - class TestTestUtils(testutils.BaseTestCase): """Let's get meta.""" @@ -34,15 +28,15 @@ def testNoCheckOnException(self): raise ValueError() def testCheckStdoutOrStderrNone(self): - with six.assertRaisesRegex(self, AssertionError, 'stdout:'): + with self.assertRaisesRegex(AssertionError, 'stdout:'): with self.assertOutputMatches(stdout=None): print('blah') - with six.assertRaisesRegex(self, AssertionError, 'stderr:'): + with self.assertRaisesRegex(AssertionError, 'stderr:'): with self.assertOutputMatches(stderr=None): print('blah', file=sys.stderr) - with six.assertRaisesRegex(self, AssertionError, 'stderr:'): + with self.assertRaisesRegex(AssertionError, 'stderr:'): with self.assertOutputMatches(stdout='apple', stderr=None): print('apple') print('blah', file=sys.stderr) diff --git a/fire/trace.py b/fire/trace.py index 7174f994..68b48ce5 100644 --- a/fire/trace.py +++ b/fire/trace.py @@ -25,11 +25,7 @@ component will be None. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import pipes +import shlex from fire import inspectutils @@ -166,8 +162,8 @@ def display(arg1, arg2='!'): def _Quote(self, arg): if arg.startswith('--') and '=' in arg: prefix, value = arg.split('=', 1) - return pipes.quote(prefix) + '=' + pipes.quote(value) - return pipes.quote(arg) + return shlex.quote(prefix) + '=' + shlex.quote(value) + return shlex.quote(arg) def GetCommand(self, include_separators=True): """Returns the command representing the trace up to this point. @@ -216,10 +212,7 @@ def NeedsSeparator(self): def __str__(self): lines = [] for index, element in enumerate(self.elements): - line = '{index}. {trace_string}'.format( - index=index + 1, - trace_string=element, - ) + line = f'{index + 1}. {element}' lines.append(line) return '\n'.join(lines) @@ -265,7 +258,7 @@ def __init__(self, Args: component: The result of this element of the trace. - action: The type of action (eg instantiating a class) taking place. + action: The type of action (e.g. instantiating a class) taking place. target: (string) The name of the component being acted upon. args: The args consumed by the represented action. filename: The file in which the action is defined, or None if N/A. @@ -305,11 +298,11 @@ def __str__(self): # Format is: {action} "{target}" ({filename}:{lineno}) string = self._action if self._target is not None: - string += ' "{target}"'.format(target=self._target) + string += f' "{self._target}"' if self._filename is not None: path = self._filename if self._lineno is not None: - path += ':{lineno}'.format(lineno=self._lineno) + path += f':{self._lineno}' - string += ' ({path})'.format(path=path) + string += f' ({path})' return string diff --git a/fire/trace_test.py b/fire/trace_test.py index 1621a593..1f858f5e 100644 --- a/fire/trace_test.py +++ b/fire/trace_test.py @@ -14,10 +14,6 @@ """Tests for the trace module.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - from fire import testutils from fire import trace diff --git a/fire/value_types.py b/fire/value_types.py index c0a137fd..81308973 100644 --- a/fire/value_types.py +++ b/fire/value_types.py @@ -14,17 +14,12 @@ """Types of values.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import inspect from fire import inspectutils -import six -VALUE_TYPES = (bool, six.string_types, six.integer_types, float, complex, +VALUE_TYPES = (bool, str, bytes, int, float, complex, type(Ellipsis), type(None), type(NotImplemented)) diff --git a/pylintrc b/pylintrc index 558d3ba2..8896bb5b 100644 --- a/pylintrc +++ b/pylintrc @@ -7,9 +7,6 @@ # pygtk.require(). #init-hook= -# Profiled execution. -profile=no - # Add to the black list. It should be a base name, not a # path. You may set this option multiple times. ignore= @@ -41,14 +38,6 @@ disable=design,similarities,no-self-use,attribute-defined-outside-init,locally-d # (visual studio) and html output-format=text -# Include message's id in output -include-ids=no - -# Put messages in a separate file for each module / package specified on the -# command line instead of printing them on stdout. Reports (if any) will be -# written in a file name "pylint_global.[txt|html]". -files-output=no - # Tells whether to display a full report or only the messages reports=yes @@ -59,10 +48,6 @@ reports=yes # (R0004). evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) -# Add a comment according to your evaluation note. This is used by the global -# evaluation report (R0004). -comment=no - [VARIABLES] @@ -79,9 +64,6 @@ additional-builtins= [BASIC] -# List of builtins function names that should not be used, separated by a comma -bad-functions=map,filter,apply,input,reduce - # Regular expression which should only match correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ @@ -114,7 +96,7 @@ inlinevar-rgx=^[a-z][a-z0-9_]*$ good-names=i,j,k,ex,main,Run,_ # Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata +bad-names=map,filter,apply,input,reduce,foo,bar,baz,toto,tutu,tata # Regular expression which should only match functions or classes name which do # not require a docstring @@ -186,7 +168,7 @@ max-locals=15 max-returns=6 # Maximum number of branch for function / method body -max-branchs=12 +max-branches=12 # Maximum number of statements in function / method body max-statements=50 diff --git a/setup.cfg b/setup.cfg index 977056b0..ed53d83b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,3 @@ -[wheel] -universal = 1 - [aliases] test = pytest diff --git a/setup.py b/setup.py index 24e0e325..82073be4 100644 --- a/setup.py +++ b/setup.py @@ -29,18 +29,15 @@ A library for automatically generating command line interfaces.""".strip() DEPENDENCIES = [ - 'six', 'termcolor', - 'enum34; python_version < "3.4"' ] TEST_DEPENDENCIES = [ 'hypothesis', - 'mock', - 'python-Levenshtein', + 'levenshtein', ] -VERSION = '0.6.0' +VERSION = '0.7.0' URL = 'https://github.com/google/python-fire' setup( @@ -63,11 +60,7 @@ 'License :: OSI Approved :: Apache Software License', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9',