Support for dynamic callables to cffi libraries
- Ngôn ngữ chính
- Python
- Star
- 28.2k
- Fork
- 1.5k
- Chỉ số merge pull request
- Không có pull request nào được merge trong 30 ngày
Mô tả
## Summary
Objects that override `__getattr__` to provide [dynamic access to a cffi](https://github.com/jcupitt/pyvips/blob/master/pyvips/vimage.py#L741) (such as [pyvips](https://github.com/jcupitt/pyvips)) are not supported by fire. (I am aware that libvips comes with it's own cli, however pyvips demonstrates this issue well).
## What doesn't work
### Attempt 1
```python
import fire
from pyvips import Image
if __name__ == "__main__":
fire.Fire(Image)
```
```bash
$ python script.py Image new_from_file IMG-2095.JPG
Traceback (most recent call last):
File "script.py", line 23, in
fire.Fire(Image)
File "/Users/rpfeffer/sandbox/pyvips_testing/venv/lib/python3.6/site-packages/fire/core.py", line 127, in Fire
component_trace = _Fire(component, args, context, name)
File "/Users/rpfeffer/sandbox/pyvips_testing/venv/lib/python3.6/site-packages/fire/core.py", line 366, in _Fire
component, remaining_args)
File "/Users/rpfeffer/sandbox/pyvips_testing/venv/lib/python3.6/site-packages/fire/core.py", line 540, in _CallCallable
(varargs, kwargs), consumed_args, remaining_args, capacity = parse(args)
File "/Users/rpfeffer/sandbox/pyvips_testing/venv/lib/python3.6/site-packages/fire/core.py", line 574, in _ParseFn
remaining_args, metadata)
File "/Users/rpfeffer/sandbox/pyvips_testing/venv/lib/python3.6/site-packages/fire/core.py", line 633, in _ParseArgs
accepts_positional_args = metadata.get(decorators.ACCEPTS_POSITIONAL_ARGS)
AttributeError: 'function' object has no attribute 'get'
```
### Attempt 2
(Same script as in attempt 1)
```
$ python script.py new_from_file IMG-2095.JPG
Traceback (most recent call last):
File "script.py", line 23, in
fire.Fire(Image)
File "/Users/rpfeffer/sandbox/pyvips_testing/venv/lib/python3.6/site-packages/fire/core.py", line 127, in Fire
component_trace = _Fire(component, args, context, name)
File "/Users/rpfeffer/sandbox/pyvips_testing/venv/lib/python3.6/site-packages/fire/core.py", line 366, in _Fire
component, remaining_args)
File "/Users/rpfeffer/sandbox/pyvips_testing/venv/lib/python3.6/site-packages/fire/core.py", line 540, in _CallCallable
(varargs, kwargs), consumed_args, remaining_args, capacity = parse(args)
File "/Users/rpfeffer/sandbox/pyvips_testing/venv/lib/python3.6/site-packages/fire/core.py", line 574, in _ParseFn
remaining_args, metadata)
File "/Users/rpfeffer/sandbox/pyvips_testing/venv/lib/python3.6/site-packages/fire/core.py", line 633, in _ParseArgs
accepts_positional_args = metadata.get(decorators.ACCEPTS_POSITIONAL_ARGS)
AttributeError: 'function' object has no attribute 'get'
```
### Attempt 3
```python
import fire
from pyvips import Image
if __name__ == "__main__":
fire.Fire(Image.new_from_file)
```
```
$ python script.py IMG-2095.JPG
Traceback (most recent call last):
File "script.py", line 22, in
fire.Fire(Image.new_from_file)
File "/Users/rpfeffer/sandbox/pyvips_testing/venv/lib/python3.6/site-packages/fire/core.py", line 163, in Fire
_PrintResult(component_trace, verbose=component_trace.verbose)
File "/Users/rpfeffer/sandbox/pyvips_testing/venv/lib/python3.6/site-packages/fire/core.py", line 221, in _PrintResult
print(helputils.HelpString(result, component_trace, verbose))
File "/Users/rpfeffer/sandbox/pyvips_testing/venv/lib/python3.6/site-packages/fire/helputils.py", line 92, in HelpString
info['usage'] = UsageString(component, trace, verbose)
File "/Users/rpfeffer/sandbox/pyvips_testing/venv/lib/python3.6/site-packages/fire/helputils.py", line 197, in UsageString
completions = completion.Completions(component, verbose)
File "/Users/rpfeffer/sandbox/pyvips_testing/venv/lib/python3.6/site-packages/fire/completion.py", line 172, in Completions
for member_name, unused_member in _Members(component, verbose)
File "/Users/rpfeffer/sandbox/pyvips_testing/venv/lib/python3.6/site-packages/fire/completion.py", line 123, in _Members
members = inspect.getmembers(component)
File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/inspect.py", line 331, in getmembers
for base in object.__bases__:
TypeError: 'function' object is not iterable
```
## The problem(s)
### Tracking Metadata state
Fire adds state to the object passed in (see [fire.decorators](https://github.com/google/python-fire/blob/master/fire/decorators.py#L82)). I'd argue that fire may see benefits in terms of reducing code complexity and deliver predictable behavior for _any and all_ python objects if this state were tracked separately. A simple dictionary `{ class: metadata }` stored internally to fire may be sufficient, though there may be better ways.
To demonstrate what I mean, look at what happens when I change the script to the one below, we see progress, but still no working result.
```python
import fire
from pyvips import Image
if __name__ == "__main__":
item = Image.new_from_file
default = {
fire.decorators.ACCEPTS_POSITIONAL_ARGS: not inspect.isclass(Image),
}
setattr(item, fire.decorators.FIRE_METADATA, default)
fire.Fire(item)
````
```
$ python script.py IMG-2095.JPG
Fire trace:
1. Initial component
2. ('The function received no value for the required argument:', 'vips_filename')
Type: function
String form:
File: ~/sandbox/pyvips_testing/venv/lib/python3.6/site-packages/pyvips/vimage.py
Line: 157
Docstring: Load an image from a file.
This method can load images in any format supported by vips. The
filename can include load options, for example::
image = pyvips.Image.new_from_file('fred.jpg[shrink=2]')
You can also supply options as keyword arguments, for example::
image = pyvips.Image.new_from_file('fred.jpg', shrink=2)
The full set of options available depend upon the load operation that
will be executed. Try something like::
$ vips jpegload
at the command-line to see a summary of the available options for the
JPEG loader.
Loading is fast: only enough of the image is loaded to be able to fill
out the header. Pixels will only be decompressed when they are needed.
Args:
vips_filename (str): The disc file to load the image from, with
optional appended arguments.
All loaders support at least the following options:
Keyword args:
memory (bool): If set True, load the image via memory rather than
via a temporary disc file. See :meth:`.new_temp_file` for
notes on where temporary files are created. Small images are
loaded via memory by default, use ``VIPS_DISC_THRESHOLD`` to
set the definition of small.
access (Access): Hint the expected access pattern for the image.
fail (bool): If set True, the loader will fail with an error on
the first serious error in the file. By default, libvips
will attempt to read everything it can from a damanged image.
Returns:
A new :class:`.Image`.
Raises:
:class:`.Error`
Usage: script.py VIPS_FILENAME [--KWARGS ...]
script.py --vips-filename VIPS_FILENAME [--KWARGS ...]
```
```
$ python script.py --vips-filename IMG-2095.JPG
Traceback (most recent call last):
File "script.py", line 23, in
fire.Fire(item)
File "/Users/rpfeffer/sandbox/pyvips_testing/venv/lib/python3.6/site-packages/fire/core.py", line 163, in Fire
_PrintResult(component_trace, verbose=component_trace.verbose)
File "/Users/rpfeffer/sandbox/pyvips_testing/venv/lib/python3.6/site-packages/fire/core.py", line 221, in _PrintResult
print(helputils.HelpString(result, component_trace, verbose))
File "/Users/rpfeffer/sandbox/pyvips_testing/venv/lib/python3.6/site-packages/fire/helputils.py", line 92, in HelpString
info['usage'] = UsageString(component, trace, verbose)
File "/Users/rpfeffer/sandbox/pyvips_testing/venv/lib/python3.6/site-packages/fire/helputils.py", line 197, in UsageString
completions = completion.Completions(component, verbose)
File "/Users/rpfeffer/sandbox/pyvips_testing/venv/lib/python3.6/site-packages/fire/completion.py", line 172, in Completions
for member_name, unused_member in _Members(component, verbose)
File "/Users/rpfeffer/sandbox/pyvips_testing/venv/lib/python3.6/site-packages/fire/completion.py", line 123, in _Members
members = inspect.getmembers(component)
File "/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/inspect.py", line 331, in getmembers
for base in object.__bases__:
TypeError: 'function' object is not iterable
```
### Handling of Function Return Types
It would appear that making calls to methods that return additional callable objects is not well supported by fire, as evidenced by the stack traces above.
# Why stuff like this should be supported:
Until this is supported, fire cannot live up to it's main stated value proposition:
> Python Fire is a library for automatically generating command line interfaces (CLIs) from absolutely any Python object.
Hướng dẫn đóng góp
Đánh giá
Issue này chưa được đánh giá.