Hello community, here is the log from the commit of package python-fire for openSUSE:Leap:15.2 checked in at 2020-04-08 12:48:51 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Leap:15.2/python-fire (Old) and /work/SRC/openSUSE:Leap:15.2/.python-fire.new.3248 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-fire" Wed Apr 8 12:48:51 2020 rev:4 rq:790942 version:0.3.0 Changes: -------- --- /work/SRC/openSUSE:Leap:15.2/python-fire/python-fire.changes 2020-03-09 18:05:24.668818874 +0100 +++ /work/SRC/openSUSE:Leap:15.2/.python-fire.new.3248/python-fire.changes 2020-04-08 12:49:21.546372749 +0200 @@ -1,0 +2,18 @@ +Thu Apr 2 01:40:27 UTC 2020 - Steve Kowalik <[email protected]> + +- Update to 0.3.0: + * Use Fire on third-party code without making any code changes: + python -m fire <module> + * Docstring parsing fix for all lines are blank f01aad3 + * Improved parsing of numpy-style docstrings + * #187 Expose built-in functions from the standard library (e.g. sin, cos) + * #149 Support objects implementing __getattr__ + * #205 Fix ctrl-C handling in help screens + * Support functools.wraps and lru_cache decorated functions + * Better support for objects with properties + * Objects with custom __str__ are now treated as Values. E.g. If such an + object appears in a dict, the dict will still print in line-by-line mode + rather than showing a help screen by default. + * Formatting on Windows works properly now + +------------------------------------------------------------------- Old: ---- fire-0.2.1.tar.gz New: ---- fire-0.3.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-fire.spec ++++++ --- /var/tmp/diff_new_pack.5WcWsb/_old 2020-04-08 12:49:22.218373100 +0200 +++ /var/tmp/diff_new_pack.5WcWsb/_new 2020-04-08 12:49:22.218373100 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-fire # -# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2020 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +18,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-fire -Version: 0.2.1 +Version: 0.3.0 Release: 0 Summary: A library for automatically generating command line interfaces License: Apache-2.0 ++++++ fire-0.2.1.tar.gz -> fire-0.3.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/PKG-INFO new/fire-0.3.0/PKG-INFO --- old/fire-0.2.1/PKG-INFO 2019-08-01 20:21:19.000000000 +0200 +++ new/fire-0.3.0/PKG-INFO 2020-03-20 20:29:32.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: fire -Version: 0.2.1 +Version: 0.3.0 Summary: A library for automatically generating command line interfaces. Home-page: https://github.com/google/python-fire Author: David Bieber diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/README.md new/fire-0.3.0/README.md --- old/fire-0.2.1/README.md 2019-07-26 19:14:05.000000000 +0200 +++ new/fire-0.3.0/README.md 2020-03-20 20:15:12.000000000 +0100 @@ -1,15 +1,19 @@ # Python Fire [](https://github.com/google/python-fire) + _Python Fire is a library for automatically generating command line interfaces (CLIs) from absolutely any Python object._ -- Python Fire is a simple way to create a CLI in Python. [[1]](docs/benefits.md#simple-cli) -- Python Fire is a helpful tool for developing and debugging Python code. [[2]](docs/benefits.md#debugging) -- Python Fire helps with exploring existing code or turning other people's code -into a CLI. [[3]](docs/benefits.md#exploring) -- Python Fire makes transitioning between Bash and Python easier. [[4]](docs/benefits.md#bash) -- Python Fire makes using a Python REPL easier by setting up the REPL with the -modules and variables you'll need already imported and created. [[5]](docs/benefits.md#repl) - +- Python Fire is a simple way to create a CLI in Python. + [[1]](docs/benefits.md#simple-cli) +- Python Fire is a helpful tool for developing and debugging Python code. + [[2]](docs/benefits.md#debugging) +- Python Fire helps with exploring existing code or turning other people's + code into a CLI. [[3]](docs/benefits.md#exploring) +- Python Fire makes transitioning between Bash and Python easier. + [[4]](docs/benefits.md#bash) +- Python Fire makes using a Python REPL easier by setting up the REPL with the + modules and variables you'll need already imported and created. + [[5]](docs/benefits.md#repl) ## Installation @@ -20,7 +24,6 @@ To install Python Fire from source, first clone the repository and then run: `python setup.py install` - ## Basic Usage You can call `Fire` on any Python object:<br> @@ -74,17 +77,14 @@ For additional examples, see [The Python Fire Guide](docs/guide.md). - ## Why is it called Fire? When you call `Fire`, it fires off (executes) your command. - ## Where can I learn more? Please see [The Python Fire Guide](docs/guide.md). - ## Reference | Setup | Command | Notes @@ -97,14 +97,14 @@ | Call | `fire.Fire()` | Turns the current module into a Fire CLI. | Call | `fire.Fire(component)` | Turns `component` into a Fire CLI. -Using a CLI | Command | Notes -:---------------------------------------------- | :-------------------------------------- | :---- -[Help](docs/using-cli.md#help-flag) | `command --help` or `command -- --help` | -[REPL](docs/using-cli.md#interactive-flag) | `command -- --interactive` | Enters interactive mode. -[Separator](docs/using-cli.md#separator-flag) | `command -- --separator=X` | Sets the separator to `X`. The default separator is `-`. -[Completion](docs/using-cli.md#completion-flag) | `command -- --completion [shell]` | Generates a completion script for the CLI. -[Trace](docs/using-cli.md#trace-flag) | `command -- --trace` | Gets a Fire trace for the command. -[Verbose](docs/using-cli.md#verbose-flag) | `command -- --verbose` | +| Using a CLI | Command | Notes +| :---------------------------------------------- | :-------------------------------------- | :---- +| [Help](docs/using-cli.md#help-flag) | `command --help` or `command -- --help` | +| [REPL](docs/using-cli.md#interactive-flag) | `command -- --interactive` | Enters interactive mode. +| [Separator](docs/using-cli.md#separator-flag) | `command -- --separator=X` | Sets the separator to `X`. The default separator is `-`. +| [Completion](docs/using-cli.md#completion-flag) | `command -- --completion [shell]` | Generates a completion script for the CLI. +| [Trace](docs/using-cli.md#trace-flag) | `command -- --trace` | Gets a Fire trace for the command. +| [Verbose](docs/using-cli.md#verbose-flag) | `command -- --verbose` | _Note that these flags are separated from the Fire command by an isolated `--`._ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/fire/__init__.py new/fire-0.3.0/fire/__init__.py --- old/fire-0.2.1/fire/__init__.py 2019-08-01 20:19:04.000000000 +0200 +++ new/fire-0.3.0/fire/__init__.py 2020-03-20 20:15:12.000000000 +0100 @@ -21,4 +21,4 @@ from fire.core import Fire __all__ = ['Fire'] -__version__ = '0.2.1' +__version__ = '0.3.0' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/fire/__main__.py new/fire-0.3.0/fire/__main__.py --- old/fire-0.2.1/fire/__main__.py 1970-01-01 01:00:00.000000000 +0100 +++ new/fire-0.3.0/fire/__main__.py 2020-03-20 20:15:12.000000000 +0100 @@ -0,0 +1,34 @@ +# Copyright (C) 2018 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# pylint: disable=invalid-name +"""Enables use of Python Fire as a "main" function (i.e. `python -m fire`). + +This allows using Fire with third-party libraries without modifying their code. +""" + +import importlib +import sys + +import fire + + +def main(args): + module_name = args[1] + module = importlib.import_module(module_name) + fire.Fire(module, name=module_name, command=args[2:]) + + +if __name__ == '__main__': + main(sys.argv) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/fire/completion.py new/fire-0.3.0/fire/completion.py --- old/fire-0.2.1/fire/completion.py 2019-07-26 19:14:05.000000000 +0200 +++ new/fire-0.3.0/fire/completion.py 2020-03-20 20:15:12.000000000 +0100 @@ -281,17 +281,6 @@ ) -def GetClassAttrsDict(component): - """Gets the attributes of the component class, as a dict with name keys.""" - if not inspect.isclass(component): - return None - class_attrs_list = inspect.classify_class_attrs(component) - return { - class_attr.name: class_attr - for class_attr in class_attrs_list - } - - def MemberVisible(component, name, member, class_attrs=None, verbose=False): """Returns whether a member should be included in auto-completion or help. @@ -309,7 +298,8 @@ name: The name of the member. member: The member itself. class_attrs: (optional) If component is a class, provide this as: - GetClassAttrsDict(component). If not provided, it will be computed. + inspectutils.GetClassAttrsDict(component). If not provided, it will be + computed. verbose: Whether to include private members. Returns A boolean value indicating whether the member should be included. @@ -318,7 +308,9 @@ return False if verbose: return True - if isinstance(member, type(absolute_import)): + if member in (absolute_import, division, print_function): + return False + if isinstance(member, type(absolute_import)) and six.PY34: return False if inspect.ismodule(member) and member is six: # TODO(dbieber): Determine more generally which modules to hide. @@ -326,7 +318,7 @@ if inspect.isclass(component): # If class_attrs has not been provided, compute it. if class_attrs is None: - class_attrs = GetClassAttrsDict(class_attrs) + class_attrs = inspectutils.GetClassAttrsDict(class_attrs) or {} class_attr = class_attrs.get(name) if class_attr and class_attr.kind in ('method', 'property'): # methods and properties should be accessed on instantiated objects, @@ -353,9 +345,9 @@ Args: component: The component whose members to list. class_attrs: (optional) If component is a class, you may provide this as: - GetClassAttrsDict(component). If not provided, it will be computed. - If provided, this determines how class members will be treated for - visibility. In particular, methods are generally hidden for + inspectutils.GetClassAttrsDict(component). If not provided, it will be + computed. If provided, this determines how class members will be treated + for visibility. In particular, methods are generally hidden for non-instantiated classes, but if you wish them to be shown (e.g. for completion scripts) then pass in a different class_attr for them. verbose: Whether to include private members. @@ -369,7 +361,7 @@ # If class_attrs has not been provided, compute it. if class_attrs is None: - class_attrs = GetClassAttrsDict(component) + class_attrs = inspectutils.GetClassAttrsDict(component) return [ (member_name, member) for member_name, member in members if MemberVisible(component, member_name, member, class_attrs=class_attrs, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/fire/console/console_io.py new/fire-0.3.0/fire/console/console_io.py --- old/fire-0.2.1/fire/console/console_io.py 2019-07-26 19:14:05.000000000 +0200 +++ new/fire-0.3.0/fire/console/console_io.py 2020-03-20 20:15:12.000000000 +0100 @@ -20,6 +20,7 @@ from __future__ import print_function import os +import signal import subprocess import sys @@ -68,6 +69,10 @@ return True +def PreexecFunc(): + signal.signal(signal.SIGINT, signal.SIG_IGN) + + def More(contents, out, prompt=None, check_pager=True): """Run a user specified pager or fall back to the internal pager. @@ -97,10 +102,19 @@ less_orig = encoding.GetEncodedValue(os.environ, 'LESS', None) less = '-R' + (less_orig or '') encoding.SetEncodedValue(os.environ, 'LESS', less) - p = subprocess.Popen(pager, stdin=subprocess.PIPE, shell=True) + # Ignores SIGINT from this point on since the child process has started + # and we don't want to terminate either one when the child is still alive. + signal.signal(signal.SIGINT, signal.SIG_IGN) + # Runs PreexecFunc before starting the child so SIGINT is ignored for the + # child process as well. + p = subprocess.Popen( + pager, stdin=subprocess.PIPE, shell=True, preexec_fn=PreexecFunc) enc = console_attr.GetConsoleAttr().GetEncoding() p.communicate(input=contents.encode(enc)) p.wait() + # Starts using default disposition for SIGINT again after the child has + # exited. + signal.signal(signal.SIGINT, signal.SIG_DFL) if less_orig is None: encoding.SetEncodedValue(os.environ, 'LESS', None) return diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/fire/core.py new/fire-0.3.0/fire/core.py --- old/fire-0.2.1/fire/core.py 2019-07-26 19:14:05.000000000 +0200 +++ new/fire-0.3.0/fire/core.py 2020-03-20 20:15:12.000000000 +0100 @@ -244,14 +244,11 @@ # and move serialization to its own module. result = component_trace.GetResult() - if hasattr(result, '__str__'): + if value_types.HasCustomStr(result): # If the object has a custom __str__ method, rather than one inherited from # object, then we use that to serialize the object. - class_attrs = completion.GetClassAttrsDict(type(result)) or {} - str_attr = class_attrs.get('__str__') - if str_attr and str_attr.defining_class is not object: - print(str(result)) - return + print(str(result)) + return if isinstance(result, (list, set, frozenset, types.GeneratorType)): for i in result: @@ -312,7 +309,7 @@ # We need to do 2 iterations over the items in the result dict # 1) Getting visible items and the longest key for output formatting # 2) Actually construct the output lines - class_attrs = completion.GetClassAttrsDict(result) + class_attrs = inspectutils.GetClassAttrsDict(result) result_visible = { key: value for key, value in result.items() if completion.MemberVisible(result, key, value, @@ -632,7 +629,7 @@ Raises: FireError: If we cannot consume an argument to get a member. """ - members = dict(inspect.getmembers(component)) + members = dir(component) arg = args[0] arg_names = [ arg, @@ -641,7 +638,7 @@ for arg_name in arg_names: if arg_name in members: - return members[arg_name], [arg], args[1:] + return getattr(component, arg_name), [arg], args[1:] raise FireError('Could not consume arg:', arg) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/fire/core_test.py new/fire-0.3.0/fire/core_test.py --- old/fire-0.2.1/fire/core_test.py 2019-07-26 19:14:05.000000000 +0200 +++ new/fire-0.3.0/fire/core_test.py 2020-03-20 20:15:12.000000000 +0100 @@ -24,6 +24,8 @@ from fire import trace import mock +import six + class CoreTest(testutils.BaseTestCase): @@ -192,5 +194,18 @@ 7, ) + @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 + command=['foo']), 'foo') + + if __name__ == '__main__': testutils.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/fire/custom_descriptions.py new/fire-0.3.0/fire/custom_descriptions.py --- old/fire-0.2.1/fire/custom_descriptions.py 2019-07-26 19:14:05.000000000 +0200 +++ new/fire-0.3.0/fire/custom_descriptions.py 2020-03-20 20:15:12.000000000 +0100 @@ -40,8 +40,12 @@ from __future__ import division from __future__ import print_function +from fire import formatting import six +TWO_DOUBLE_QUOTES = '""' +STRING_DESC_PREFIX = 'The string ' + def NeedsCustomDescription(component): """Whether the component should use a custom description and summary. @@ -69,3 +73,79 @@ ): return True return False + + +def GetStringTypeSummary(obj, available_space, line_length): + """Returns a custom summary for string type objects. + + This function constructs a summary for string type objects by double quoting + the string value. The double quoted string value will be potentially truncated + with ellipsis depending on whether it has enough space available to show the + full string value. + + Args: + obj: The object to generate summary for. + available_space: Number of character spaces available. + line_length: The full width of the terminal, default is 80. + + Returns: + A summary for the input object. + """ + if len(obj) + len(TWO_DOUBLE_QUOTES) <= available_space: + content = obj + else: + additional_len_needed = len(TWO_DOUBLE_QUOTES) + len(formatting.ELLIPSIS) + if available_space < additional_len_needed: + available_space = line_length + content = formatting.EllipsisTruncate( + obj, available_space - len(TWO_DOUBLE_QUOTES), line_length) + return formatting.DoubleQuote(content) + + +def GetStringTypeDescription(obj, available_space, line_length): + """Returns the predefined description for string obj. + + This function constructs a description for string type objects in the format + of 'The string "<string_value>"'. <string_value> could be potentially + truncated depending on whether it has enough space available to show the full + string value. + + Args: + obj: The object to generate description for. + available_space: Number of character spaces available. + line_length: The full width of the terminal, default if 80. + + Returns: + A description for input object. + """ + additional_len_needed = len(STRING_DESC_PREFIX) + len( + TWO_DOUBLE_QUOTES) + len(formatting.ELLIPSIS) + if available_space < additional_len_needed: + available_space = line_length + + return STRING_DESC_PREFIX + formatting.DoubleQuote( + formatting.EllipsisTruncate( + obj, available_space - len(STRING_DESC_PREFIX) - + len(TWO_DOUBLE_QUOTES), line_length)) + + +CUSTOM_DESC_SUM_FN_DICT = { + 'str': (GetStringTypeSummary, GetStringTypeDescription), + 'unicode': (GetStringTypeSummary, GetStringTypeDescription), +} + + +def GetSummary(obj, available_space, line_length): + obj_type_name = type(obj).__name__ + if obj_type_name in CUSTOM_DESC_SUM_FN_DICT.keys(): + return CUSTOM_DESC_SUM_FN_DICT.get(obj_type_name)[0](obj, available_space, + line_length) + return None + + +def GetDescription(obj, available_space, line_length): + obj_type_name = type(obj).__name__ + if obj_type_name in CUSTOM_DESC_SUM_FN_DICT.keys(): + return CUSTOM_DESC_SUM_FN_DICT.get(obj_type_name)[1](obj, available_space, + line_length) + return None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/fire/custom_descriptions_test.py new/fire-0.3.0/fire/custom_descriptions_test.py --- old/fire-0.2.1/fire/custom_descriptions_test.py 1970-01-01 01:00:00.000000000 +0100 +++ new/fire-0.3.0/fire/custom_descriptions_test.py 2020-03-20 20:15:12.000000000 +0100 @@ -0,0 +1,73 @@ +# Copyright (C) 2018 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""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 + +LINE_LENGTH = 80 + + +class CustomDescriptionTest(testutils.BaseTestCase): + + def test_string_type_summary_enough_space(self): + component = 'Test' + summary = custom_descriptions.GetSummary( + obj=component, available_space=80, line_length=LINE_LENGTH) + self.assertEqual(summary, '"Test"') + + def test_string_type_summary_not_enough_space_truncated(self): + component = 'Test' + summary = custom_descriptions.GetSummary( + obj=component, available_space=5, line_length=LINE_LENGTH) + self.assertEqual(summary, '"..."') + + def test_string_type_summary_not_enough_space_new_line(self): + component = 'Test' + summary = custom_descriptions.GetSummary( + obj=component, available_space=4, line_length=LINE_LENGTH) + self.assertEqual(summary, '"Test"') + + def test_string_type_summary_not_enough_space_long_truncated(self): + component = 'Lorem ipsum dolor sit amet' + summary = custom_descriptions.GetSummary( + obj=component, available_space=10, line_length=LINE_LENGTH) + self.assertEqual(summary, '"Lorem..."') + + def test_string_type_description_enough_space(self): + component = 'Test' + description = custom_descriptions.GetDescription( + obj=component, available_space=80, line_length=LINE_LENGTH) + self.assertEqual(description, 'The string "Test"') + + def test_string_type_description_not_enough_space_truncated(self): + component = 'Lorem ipsum dolor sit amet' + description = custom_descriptions.GetDescription( + obj=component, available_space=20, line_length=LINE_LENGTH) + self.assertEqual(description, 'The string "Lore..."') + + def test_string_type_description_not_enough_space_new_line(self): + component = 'Lorem ipsum dolor sit amet' + description = custom_descriptions.GetDescription( + obj=component, available_space=10, line_length=LINE_LENGTH) + self.assertEqual(description, 'The string "Lorem ipsum dolor sit amet"') + + +if __name__ == '__main__': + testutils.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/fire/docstrings.py new/fire-0.3.0/fire/docstrings.py --- old/fire-0.2.1/fire/docstrings.py 2019-08-01 20:19:04.000000000 +0200 +++ new/fire-0.3.0/fire/docstrings.py 2020-03-20 20:15:12.000000000 +0100 @@ -149,6 +149,7 @@ Args: docstring: The docstring to parse. + Returns: A DocstringInfo containing information about the docstring. """ @@ -175,8 +176,9 @@ for index, line in enumerate(lines): has_next = index + 1 < lines_len + previous_line = lines[index - 1] if index > 0 else None next_line = lines[index + 1] if has_next else None - line_info = _create_line_info(line, next_line) + line_info = _create_line_info(line, next_line, previous_line) _consume_line(line_info, state) summary = ' '.join(state.summary.lines) if state.summary.lines else None @@ -212,7 +214,8 @@ """ # Find the first non-blank line. start = 0 - while lines and _is_blank(lines[start]): + num_lines = len(lines) + while lines and start < num_lines and _is_blank(lines[start]): start += 1 lines = lines[start:] @@ -453,7 +456,7 @@ # of the previous arg, or a new arg. TODO: Whitespace can distinguish. arg = _get_or_create_arg_by_name(state, line_stripped) state.current_arg = arg - elif ':' in line_stripped: + elif _line_is_numpy_parameter_type(line_info): possible_args, type_data = line_stripped.split(':', 1) arg_names = _as_arg_names(possible_args) # re.split(' |,', s) if arg_names: @@ -490,8 +493,8 @@ pass -def _create_line_info(line, next_line): - """Returns information about the current and next line of the docstring.""" +def _create_line_info(line, next_line, previous_line): + """Returns information about the current line and surrounding lines.""" line_info = Namespace() # TODO(dbieber): Switch to an explicit class. line_info.line = line line_info.stripped = line.strip() @@ -504,6 +507,11 @@ line_info.next.stripped = next_line.strip() if next_line_exists else None line_info.next.indentation = ( len(next_line) - len(next_line.lstrip()) if next_line_exists else None) + line_info.previous.line = previous_line + previous_line_exists = previous_line is not None + line_info.previous.indentation = ( + len(previous_line) - + len(previous_line.lstrip()) if previous_line_exists else None) # Note: This counts all whitespace equally. return line_info @@ -724,3 +732,29 @@ return _section_from_possible_title(possible_title) else: return None + + +def _line_is_numpy_parameter_type(line_info): + """Returns whether the line contains a numpy style parameter type definition. + + We look for a line of the form: + x : type + + And we have to exclude false positives on argument descriptions containing a + colon by checking the indentation of the line above. + + Args: + line_info: Information about the current line. + Returns: + True if the line is a numpy parameter type definition, False otherwise. + """ + line_stripped = line_info.remaining.strip() + if ':' in line_stripped: + previous_indent = line_info.previous.indentation + current_indent = line_info.indentation + if ':' in line_info.previous.line and current_indent > previous_indent: + # The parameter type was the previous line; this is the description. + return False + else: + return True + return False diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/fire/docstrings_test.py new/fire-0.3.0/fire/docstrings_test.py --- old/fire-0.2.1/fire/docstrings_test.py 2019-08-01 20:19:04.000000000 +0200 +++ new/fire-0.3.0/fire/docstrings_test.py 2020-03-20 20:15:12.000000000 +0100 @@ -253,6 +253,32 @@ self.assertEqual(expected_output, docstrings._strip_blank_lines(lines)) # pylint: disable=protected-access + def test_numpy_colon_in_description(self): + docstring = """ + Greets name. + + Arguments + --------- + name : str + name, default : World + arg2 : int + arg2, default:None + arg3 : bool + """ + docstring_info = docstrings.parse(docstring) + expected_docstring_info = DocstringInfo( + summary='Greets name.', + description=None, + args=[ + ArgInfo(name='name', type='str', + description='name, default : World'), + ArgInfo(name='arg2', type='int', + description='arg2, default:None'), + ArgInfo(name='arg3', type='bool', description=None), + ] + ) + self.assertEqual(expected_docstring_info, docstring_info) + if __name__ == '__main__': testutils.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/fire/fire_test.py new/fire-0.3.0/fire/fire_test.py --- old/fire-0.2.1/fire/fire_test.py 2019-07-26 19:14:05.000000000 +0200 +++ new/fire-0.3.0/fire/fire_test.py 2020-03-20 20:15:12.000000000 +0100 @@ -20,7 +20,6 @@ import os import sys -import unittest import fire from fire import test_components as tc @@ -185,7 +184,7 @@ self.assertEqual(fire.Fire(tc.Annotations, command=['double', '5']), 10) self.assertEqual(fire.Fire(tc.Annotations, command=['triple', '5']), 15) - @unittest.skipIf(six.PY2, 'Keyword-only arguments not in Python 2.') + @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. @@ -703,6 +702,25 @@ with self.assertRaisesFireExit(2): fire.Fire(tc.InstanceVars, command=['--arg1=a1', '--arg2=a2', '-', 'jog']) + def testClassWithDefaultMethod(self): + self.assertEqual( + fire.Fire(tc.DefaultMethod, command=['double', '10']), 20 + ) + + def testClassWithInvalidProperty(self): + self.assertEqual( + 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): + fire.Fire(tc.decorated_method, command=['-h']) + with self.assertRaisesFireExit(0): + fire.Fire(tc.decorated_method, command=['--help']) + if __name__ == '__main__': testutils.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/fire/formatting.py new/fire-0.3.0/fire/formatting.py --- old/fire-0.2.1/fire/formatting.py 2019-07-26 19:14:05.000000000 +0200 +++ new/fire-0.3.0/fire/formatting.py 2020-03-20 20:15:12.000000000 +0100 @@ -18,9 +18,13 @@ from __future__ import division from __future__ import print_function +from fire import formatting_windows # pylint: disable=unused-import import termcolor +ELLIPSIS = '...' + + def Indent(text, spaces=2): lines = text.split('\n') return '\n'.join( @@ -65,3 +69,29 @@ def Error(text): return termcolor.colored(text, color='red', attrs=['bold']) + + +def EllipsisTruncate(text, available_space, line_length): + """Truncate text from the end with ellipsis.""" + if available_space < len(ELLIPSIS): + available_space = line_length + # No need to truncate + if len(text) <= available_space: + return text + return text[:available_space - len(ELLIPSIS)] + ELLIPSIS + + +def EllipsisMiddleTruncate(text, available_space, line_length): + """Truncates text from the middle with ellipsis.""" + if available_space < len(ELLIPSIS): + available_space = line_length + if len(text) < available_space: + return text + available_string_len = available_space - len(ELLIPSIS) + first_half_len = int(available_string_len / 2) # start from middle + second_half_len = available_string_len - first_half_len + return text[:first_half_len] + ELLIPSIS + text[-second_half_len:] + + +def DoubleQuote(text): + return '"%s"' % text diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/fire/formatting_test.py new/fire-0.3.0/fire/formatting_test.py --- old/fire-0.2.1/fire/formatting_test.py 2019-07-26 19:14:05.000000000 +0200 +++ new/fire-0.3.0/fire/formatting_test.py 2020-03-20 20:15:12.000000000 +0100 @@ -21,6 +21,8 @@ from fire import formatting from fire import testutils +LINE_LENGTH = 80 + class FormattingTest(testutils.BaseTestCase): @@ -51,6 +53,30 @@ 'chicken |', 'cheese'], lines) + def test_ellipsis_truncate(self): + text = 'This is a string' + truncated_text = formatting.EllipsisTruncate( + text=text, available_space=10, line_length=LINE_LENGTH) + self.assertEqual('This is...', truncated_text) + + def test_ellipsis_truncate_not_enough_space(self): + text = 'This is a string' + truncated_text = formatting.EllipsisTruncate( + text=text, available_space=2, line_length=LINE_LENGTH) + self.assertEqual('This is a string', truncated_text) + + def test_ellipsis_middle_truncate(self): + text = '1000000000L' + truncated_text = formatting.EllipsisMiddleTruncate( + text=text, available_space=7, line_length=LINE_LENGTH) + self.assertEqual('10...0L', truncated_text) + + def test_ellipsis_middle_truncate_not_enough_space(self): + text = '1000000000L' + truncated_text = formatting.EllipsisMiddleTruncate( + text=text, available_space=2, line_length=LINE_LENGTH) + self.assertEqual('1000000000L', truncated_text) + if __name__ == '__main__': testutils.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/fire/formatting_windows.py new/fire-0.3.0/fire/formatting_windows.py --- old/fire-0.2.1/fire/formatting_windows.py 1970-01-01 01:00:00.000000000 +0100 +++ new/fire-0.3.0/fire/formatting_windows.py 2020-03-20 20:15:12.000000000 +0100 @@ -0,0 +1,60 @@ +# Copyright (C) 2018 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""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 +import subprocess +import sys + +try: + import colorama # pylint: disable=g-import-not-at-top, # pytype: disable=import-error + HAS_COLORAMA = True +except ImportError: + HAS_COLORAMA = False + + +def initialize_or_disable(): + """Enables ANSI processing on Windows or disables it as needed.""" + if HAS_COLORAMA: + wrap = True + if sys.stdout.isatty() and platform.release() == '10': + # Enables native ANSI sequences in console. + # Windows 10, 2016, and 2019 only. + + wrap = False + kernel32 = ctypes.windll.kernel32 # pytype: disable=module-attr + enable_virtual_terminal_processing = 0x04 + out_handle = kernel32.GetStdHandle(subprocess.STD_OUTPUT_HANDLE) # pylint: disable=line-too-long, # pytype: disable=module-attr + # GetConsoleMode fails if the terminal isn't native. + mode = ctypes.wintypes.DWORD() + if kernel32.GetConsoleMode(out_handle, ctypes.byref(mode)) == 0: + wrap = True + if not mode.value & enable_virtual_terminal_processing: + if kernel32.SetConsoleMode( + out_handle, mode.value | enable_virtual_terminal_processing) == 0: + # kernel32.SetConsoleMode to enable ANSI sequences failed + wrap = True + colorama.init(wrap=wrap) + else: + os.environ['ANSI_COLORS_DISABLED'] = '1' + +if sys.platform.startswith('win'): + initialize_or_disable() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/fire/helptext.py new/fire-0.3.0/fire/helptext.py --- old/fire-0.2.1/fire/helptext.py 2019-07-26 19:14:05.000000000 +0200 +++ new/fire-0.3.0/fire/helptext.py 2020-03-20 20:15:12.000000000 +0100 @@ -33,6 +33,8 @@ from __future__ import division from __future__ import print_function +import itertools + from fire import completion from fire import custom_descriptions from fire import decorators @@ -41,10 +43,11 @@ from fire import value_types LINE_LENGTH = 80 +SECTION_INDENTATION = 4 def HelpText(component, trace=None, verbose=False): - """Gets the help string for the current component, suitalbe for a help screen. + """Gets the help string for the current component, suitable for a help screen. Args: component: The component to construct the help string for. @@ -96,10 +99,12 @@ current_command = _GetCurrentCommand(trace, include_separators=verbose) summary = _GetSummary(info) - # If the docstring is one of the messy builtin docstrings, don't show summary. - # TODO(dbieber): In follow up commits we can add in replacement summaries. + # If the docstring is one of the messy builtin docstrings, show custom one. if custom_descriptions.NeedsCustomDescription(component): - summary = None + available_space = LINE_LENGTH - SECTION_INDENTATION - len(current_command + + ' - ') + summary = custom_descriptions.GetSummary(component, available_space, + LINE_LENGTH) if summary: text = current_command + ' - ' + summary @@ -147,21 +152,28 @@ Returns the description if available. If not, returns the summary. If neither are available, returns None. """ - # If the docstring is one of the messy builtin docstrings, set it to None. - # TODO(dbieber): In follow up commits we can add in replacement docstrings. if custom_descriptions.NeedsCustomDescription(component): - return None - - summary = _GetSummary(info) - description = _GetDescription(info) + available_space = LINE_LENGTH - SECTION_INDENTATION + description = custom_descriptions.GetDescription(component, available_space, + LINE_LENGTH) + summary = custom_descriptions.GetSummary(component, available_space, + LINE_LENGTH) + else: + description = _GetDescription(info) + summary = _GetSummary(info) + # Fall back to summary if description is not available. text = description or summary or None - if text: return ('DESCRIPTION', text) else: return None +def _CreateKeywordOnlyFlagItem(flag, docstring_info, spec): + return _CreateFlagItem( + flag, docstring_info, required=flag not in spec.kwonlydefaults) + + def _ArgsAndFlagsSections(info, spec, metadata): """The "Args and Flags" sections of the help string.""" args_with_no_defaults = spec.args[:len(spec.args) - len(spec.defaults)] @@ -194,15 +206,15 @@ ('NOTES', 'You can also use flags syntax for POSITIONAL ARGUMENTS') ) - optional_flag_items = [ + positional_flag_items = [ _CreateFlagItem(flag, docstring_info, required=False) for flag in args_with_defaults ] - required_flag_items = [ - _CreateFlagItem(flag, docstring_info, required=True) + kwonly_flag_items = [ + _CreateKeywordOnlyFlagItem(flag, docstring_info, spec) for flag in spec.kwonlyargs ] - flag_items = optional_flag_items + required_flag_items + flag_items = positional_flag_items + kwonly_flag_items if spec.varkw: description = _GetArgDescription(spec.varkw, docstring_info) @@ -348,8 +360,9 @@ def _CreateOutputSection(name, content): return """{name} -{content}""".format(name=formatting.Bold(name), - content=formatting.Indent(content, 4)) +{content}""".format( + name=formatting.Bold(name), + content=formatting.Indent(content, SECTION_INDENTATION)) def _CreateArgItem(arg, docstring_info): @@ -359,6 +372,7 @@ arg: The name of the positional argument. docstring_info: A docstrings.DocstringInfo namedtuple with information about the containing function's docstring. + Returns: A string to be used in constructing the help screen for the function. """ @@ -375,9 +389,7 @@ flag: The name of the flag. docstring_info: A docstrings.DocstringInfo namedtuple with information about the containing function's docstring. - required: Whether the flag is required. Keyword-only arguments (only in - Python 3) become required flags, whereas normal keyword arguments become - optional flags. + required: Whether the flag is required. Returns: A string to be used in constructing the help screen for the function. """ @@ -418,6 +430,9 @@ if (docstring_info and not custom_descriptions.NeedsCustomDescription(member)): summary = docstring_info.summary + elif custom_descriptions.NeedsCustomDescription(member): + summary = custom_descriptions.GetSummary( + member, LINE_LENGTH - SECTION_INDENTATION, LINE_LENGTH) else: summary = None item = _CreateItem(name, summary) @@ -560,13 +575,21 @@ return items +def _KeywordOnlyArguments(spec, required=True): + return (flag for flag in spec.kwonlyargs + if required == (flag in spec.kwonlydefaults)) + + def _GetCallableAvailabilityLines(spec): """The list of availability lines for a callable for use in a usage string.""" 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 args_with_defaults] - required_flags = [('--' + flag) for flag in spec.kwonlyargs] + optional_flags = [('--' + flag) for flag in itertools.chain( + args_with_defaults, _KeywordOnlyArguments(spec, required=False))] + required_flags = [ + ('--' + flag) for flag in _KeywordOnlyArguments(spec, required=True) + ] # Flags section: availability_lines = [] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/fire/helptext_test.py new/fire-0.3.0/fire/helptext_test.py --- old/fire-0.2.1/fire/helptext_test.py 2019-07-26 19:14:05.000000000 +0200 +++ new/fire-0.3.0/fire/helptext_test.py 2020-03-20 20:15:12.000000000 +0100 @@ -26,6 +26,7 @@ from fire import test_components as tc from fire import testutils from fire import trace +import six class HelpTest(testutils.BaseTestCase): @@ -111,7 +112,8 @@ trace=trace.FireTrace(component, 'list')) self.assertIn('NAME\n list', help_screen) self.assertIn('SYNOPSIS\n list COMMAND', help_screen) - # The list docstring is messy, so it is not shown. + # TODO(zuhaochen): Change assertion after custom description is + # implemented for list type. self.assertNotIn('DESCRIPTION', help_screen) # We don't check the listed commands either since the list API could # potentially change between Python versions. @@ -125,7 +127,8 @@ trace=trace.FireTrace(component, 'list')) self.assertIn('NAME\n list', help_screen) self.assertIn('SYNOPSIS\n list COMMAND', help_screen) - # The list docstring is messy, so it is not shown. + # TODO(zuhaochen): Change assertion after custom description is + # implemented for list type. self.assertNotIn('DESCRIPTION', help_screen) # We don't check the listed commands comprehensively since the list API @@ -141,7 +144,8 @@ component=component, trace=trace.FireTrace(component, '7')) self.assertIn('NAME\n 7', help_screen) self.assertIn('SYNOPSIS\n 7 COMMAND | VALUE', help_screen) - # The int docstring is messy, so it is not shown. + # TODO(zuhaochen): Change assertion after implementing custom + # description for int. self.assertNotIn('DESCRIPTION', help_screen) self.assertIn('COMMANDS\n COMMAND is one of the following:\n', help_screen) @@ -155,6 +159,24 @@ 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( + component=component, trace=trace.FireTrace(component, 'with_default')) + self.assertIn('NAME\n with_default', output) + self.assertIn('FLAGS\n --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( + component=component, trace=trace.FireTrace(component, 'double')) + self.assertIn('NAME\n double', output) + self.assertIn('FLAGS\n --count=COUNT (required)', output) + def testHelpScreen(self): component = tc.ClassWithDocstring() t = trace.FireTrace(component, name='ClassWithDocstring') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/fire/inspectutils.py new/fire-0.3.0/fire/inspectutils.py --- old/fire-0.2.1/fire/inspectutils.py 2019-07-26 19:14:05.000000000 +0200 +++ new/fire-0.3.0/fire/inspectutils.py 2020-03-20 20:15:12.000000000 +0100 @@ -19,6 +19,9 @@ from __future__ import print_function import inspect +import sys +import types + from fire import docstrings import six @@ -70,7 +73,7 @@ """ skip_arg = False if inspect.isclass(fn): - # If the function is a class, we try to use it's init method. + # 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__ @@ -78,8 +81,11 @@ # If the function is a bound method, we skip the `self` argument. skip_arg = fn.__self__ is not None elif inspect.isbuiltin(fn): - # If the function is a bound builtin, we skip the `self` argument. - skip_arg = fn.__self__ is not None + # If the function is a bound builtin, we skip the `self` argument, unless + # the function is from a standard library module in which case its __self__ + # attribute is that module. + if not isinstance(fn.__self__, types.ModuleType): + skip_arg = True elif not inspect.isfunction(fn): # The purpose of this else clause is to set skip_arg for callable objects. skip_arg = True @@ -96,19 +102,97 @@ raise +def Py3GetFullArgSpec(fn): + """A alternative to the builtin getfullargspec. + + The builtin inspect.getfullargspec uses: + `skip_bound_args=False, follow_wrapped_chains=False` + in order to be backwards compatible. + + This function instead skips bound args (self) and follows wrapped chains. + + Args: + fn: The function or class of interest. + Returns: + An inspect.FullArgSpec namedtuple with the full arg spec of the function. + """ + # pylint: disable=no-member + # pytype: disable=module-attr + try: + sig = inspect._signature_from_callable( # pylint: disable=protected-access + fn, + skip_bound_arg=True, + follow_wrapper_chains=True, + sigcls=inspect.Signature) + except Exception: + # 'signature' can raise ValueError (most common), AttributeError, and + # possibly others. We catch all exceptions here, and reraise a TypeError. + raise TypeError('Unsupported callable.') + + args = [] + varargs = None + varkw = None + kwonlyargs = [] + defaults = () + annotations = {} + defaults = () + kwdefaults = {} + + if sig.return_annotation is not sig.empty: + annotations['return'] = sig.return_annotation + + for param in sig.parameters.values(): + kind = param.kind + name = param.name + + # pylint: disable=protected-access + if kind is inspect._POSITIONAL_ONLY: + args.append(name) + elif kind is inspect._POSITIONAL_OR_KEYWORD: + args.append(name) + if param.default is not param.empty: + defaults += (param.default,) + elif kind is inspect._VAR_POSITIONAL: + varargs = name + elif kind is inspect._KEYWORD_ONLY: + kwonlyargs.append(name) + if param.default is not param.empty: + kwdefaults[name] = param.default + elif kind is inspect._VAR_KEYWORD: + varkw = name + if param.annotation is not param.empty: + annotations[name] = param.annotation + # pylint: enable=protected-access + + if not kwdefaults: + # compatibility with 'func.__kwdefaults__' + kwdefaults = None + + if not defaults: + # compatibility with 'func.__defaults__' + defaults = None + return inspect.FullArgSpec(args, varargs, varkw, defaults, + kwonlyargs, kwdefaults, annotations) + # pylint: enable=no-member + # pytype: enable=module-attr + + def GetFullArgSpec(fn): """Returns a FullArgSpec describing the given callable.""" original_fn = fn fn, skip_arg = _GetArgSpecInfo(fn) try: - if six.PY2: + if sys.version_info[0:2] >= (3, 5): + (args, varargs, varkw, defaults, + kwonlyargs, kwonlydefaults, annotations) = Py3GetFullArgSpec(fn) + elif six.PY3: # 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) - else: - (args, varargs, varkw, defaults, - kwonlyargs, kwonlydefaults, annotations) = inspect.getfullargspec(fn) # pylint: disable=deprecated-method,no-member except TypeError: # If we can't get the argspec, how do we know if the fn should take args? @@ -137,9 +221,10 @@ # Case 3: Other known slot wrappers do not accept args. return FullArgSpec() - if skip_arg and args: + # In Python 3.5+ Py3GetFullArgSpec uses skip_bound_arg=True already. + skip_arg_required = six.PY2 or 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, kwonlyargs, kwonlydefaults, annotations) @@ -191,7 +276,7 @@ A dict with information about the component. """ try: - from IPython.core import oinspect # pylint: disable=g-import-not-at-top + from IPython.core import oinspect # pylint: disable=import-outside-toplevel,g-import-not-at-top inspector = oinspect.Inspector() info = inspector.info(component) @@ -264,3 +349,14 @@ has_fields = bool(getattr(component, '_fields', None)) return has_fields + + +def GetClassAttrsDict(component): + """Gets the attributes of the component class, as a dict with name keys.""" + if not inspect.isclass(component): + return None + class_attrs_list = inspect.classify_class_attrs(component) + return { + class_attr.name: class_attr + for class_attr in class_attrs_list + } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/fire/interact.py new/fire-0.3.0/fire/interact.py --- old/fire-0.2.1/fire/interact.py 2019-07-26 19:14:05.000000000 +0200 +++ new/fire-0.3.0/fire/interact.py 2020-03-20 20:15:12.000000000 +0100 @@ -89,11 +89,11 @@ Values are variable values. argv: The argv to use for starting ipython. Defaults to an empty list. """ - import IPython # pylint: disable=g-import-not-at-top + import IPython # pylint: disable=import-outside-toplevel,g-import-not-at-top argv = argv or [] IPython.start_ipython(argv=argv, user_ns=variables) def _EmbedCode(variables): - import code # pylint: disable=g-import-not-at-top + import code # pylint: disable=import-outside-toplevel,g-import-not-at-top code.InteractiveConsole(variables).interact() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/fire/main_test.py new/fire-0.3.0/fire/main_test.py --- old/fire-0.2.1/fire/main_test.py 1970-01-01 01:00:00.000000000 +0100 +++ new/fire-0.3.0/fire/main_test.py 2020-03-20 20:15:12.000000000 +0100 @@ -0,0 +1,42 @@ +# Copyright (C) 2018 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Test using Fire via `python -m fire`.""" + +import os + +from fire import __main__ +from fire import testutils + + +class MainModuleTest(testutils.BaseTestCase): + """Tests to verify the behavior of __main__ (python -m fire).""" + + def testNameSetting(self): + # Confirm one of the usage lines has the gettempdir member. + with self.assertOutputMatches('gettempdir'): + __main__.main(['__main__.py', 'tempfile']) + + def testArgPassing(self): + expected = os.path.join('part1', 'part2', 'part3') + with self.assertOutputMatches('%s\n' % expected): + __main__.main(['__main__.py', 'os.path', 'join', 'part1', 'part2', + 'part3']) + with self.assertOutputMatches('%s\n' % expected): + __main__.main(['__main__.py', 'os', 'path', '-', 'join', 'part1', + 'part2', 'part3']) + + +if __name__ == '__main__': + testutils.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/fire/test_components.py new/fire-0.3.0/fire/test_components.py --- old/fire-0.2.1/fire/test_components.py 2019-08-01 20:19:04.000000000 +0200 +++ new/fire-0.3.0/fire/test_components.py 2020-03-20 20:15:12.000000000 +0100 @@ -19,6 +19,7 @@ from __future__ import print_function import collections +import functools import enum import six @@ -498,3 +499,36 @@ def set(self, value): self.pixels[self._row][self._col] = value return self + + +class DefaultMethod(object): + + def double(self, number): + return 2 * number + + def __getattr__(self, name): + def _missing(): + return 'Undefined function' + return _missing + + +class InvalidProperty(object): + + def double(self, number): + return 2 * number + + @property + def prop(self): + raise ValueError('test') + + +def simple_decorator(f): + @functools.wraps(f) + def wrapper(*args, **kwargs): + return f(*args, **kwargs) + return wrapper + + +@simple_decorator +def decorated_method(name='World'): + return 'Hello %s' % name diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/fire/test_components_py3.py new/fire-0.3.0/fire/test_components_py3.py --- old/fire-0.2.1/fire/test_components_py3.py 2019-07-26 19:14:05.000000000 +0200 +++ new/fire-0.3.0/fire/test_components_py3.py 2020-03-20 20:15:12.000000000 +0100 @@ -12,9 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +# Lint as: python3 """This module has components that use Python 3 specific syntax.""" +import functools + +# pylint: disable=keyword-arg-before-vararg def identity(arg1, arg2: int, arg3=10, arg4: int = 20, *arg5, arg6, arg7: int, arg8=30, arg9: int = 40, **arg10): return arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 @@ -27,3 +31,18 @@ def triple(self, *, count): return count * 3 + + def with_default(self, *, x="x"): + print("x: " + x) + + +class LruCacheDecoratedMethod(object): + + @functools.lru_cache() + def lru_cache_in_class(self, arg1): + return arg1 + + [email protected]_cache() +def lru_cache_decorated(arg1): + return arg1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/fire/testutils.py new/fire-0.3.0/fire/testutils.py --- old/fire-0.2.1/fire/testutils.py 2019-07-26 19:14:05.000000000 +0200 +++ new/fire-0.3.0/fire/testutils.py 2020-03-20 20:15:12.000000000 +0100 @@ -98,4 +98,5 @@ # pylint: disable=invalid-name main = unittest.main skip = unittest.skip +skipIf = unittest.skipIf # pylint: enable=invalid-name diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/fire/value_types.py new/fire-0.3.0/fire/value_types.py --- old/fire-0.2.1/fire/value_types.py 2019-07-26 19:14:05.000000000 +0200 +++ new/fire-0.3.0/fire/value_types.py 2020-03-20 20:15:12.000000000 +0100 @@ -20,6 +20,7 @@ import inspect +from fire import inspectutils import six @@ -37,7 +38,7 @@ def IsValue(component): - return isinstance(component, VALUE_TYPES) + return isinstance(component, VALUE_TYPES) or HasCustomStr(component) def IsSimpleGroup(component): @@ -57,3 +58,28 @@ if not IsValue(value) and not isinstance(value, (list, dict)): return False return True + + +def HasCustomStr(component): + """Determines if a component has a custom __str__ method. + + Uses inspect.classify_class_attrs to determine the origin of the object's + __str__ method, if one is present. If it defined by `object` itself, then + it is not considered custom. Otherwise it is. This means that the __str__ + methods of primitives like ints and floats are considered custom. + + Objects with custom __str__ methods are treated as values and can be + serialized in places where more complex objects would have their help screen + shown instead. + + Args: + component: The object to check for a custom __str__ method. + Returns: + Whether `component` has a custom __str__ method. + """ + if hasattr(component, '__str__'): + class_attrs = inspectutils.GetClassAttrsDict(type(component)) or {} + str_attr = class_attrs.get('__str__') + if str_attr and str_attr.defining_class is not object: + return True + return False diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/fire.egg-info/PKG-INFO new/fire-0.3.0/fire.egg-info/PKG-INFO --- old/fire-0.2.1/fire.egg-info/PKG-INFO 2019-08-01 20:21:19.000000000 +0200 +++ new/fire-0.3.0/fire.egg-info/PKG-INFO 2020-03-20 20:29:31.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: fire -Version: 0.2.1 +Version: 0.3.0 Summary: A library for automatically generating command line interfaces. Home-page: https://github.com/google/python-fire Author: David Bieber diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/fire.egg-info/SOURCES.txt new/fire-0.3.0/fire.egg-info/SOURCES.txt --- old/fire-0.2.1/fire.egg-info/SOURCES.txt 2019-08-01 20:21:19.000000000 +0200 +++ new/fire-0.3.0/fire.egg-info/SOURCES.txt 2020-03-20 20:29:32.000000000 +0100 @@ -4,11 +4,13 @@ setup.cfg setup.py fire/__init__.py +fire/__main__.py fire/completion.py fire/completion_test.py fire/core.py fire/core_test.py fire/custom_descriptions.py +fire/custom_descriptions_test.py fire/decorators.py fire/decorators_test.py fire/docstrings.py @@ -18,12 +20,14 @@ fire/fire_test.py fire/formatting.py fire/formatting_test.py +fire/formatting_windows.py fire/helptext.py fire/helptext_test.py fire/inspectutils.py fire/inspectutils_test.py fire/interact.py fire/interact_test.py +fire/main_test.py fire/parser.py fire/parser_fuzz_test.py fire/parser_test.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fire-0.2.1/setup.py new/fire-0.3.0/setup.py --- old/fire-0.2.1/setup.py 2019-08-01 20:19:04.000000000 +0200 +++ new/fire-0.3.0/setup.py 2020-03-20 20:15:12.000000000 +0100 @@ -14,9 +14,9 @@ """The setup.py file for Python Fire.""" +import sys from setuptools import setup - LONG_DESCRIPTION = """ Python Fire is a library for automatically generating command line interfaces (CLIs) with a single line of code. @@ -32,7 +32,7 @@ DEPENDENCIES = [ 'six', 'termcolor', -] +] + (['enum34'] if sys.version < '3.4' else []) TEST_DEPENDENCIES = [ 'hypothesis', @@ -40,7 +40,7 @@ 'python-Levenshtein', ] -VERSION = '0.2.1' +VERSION = '0.3.0' URL = 'https://github.com/google/python-fire' setup(
