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 
[![PyPI](https://img.shields.io/pypi/pyversions/fire.svg?style=plastic)](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(


Reply via email to