Repository: just-work/fffw

Issue:
Incorrect as_pairs behavior if default is not set
```python
  File "/data/encoder/virtualenv/lib/python3.6/site-packages/fffw/wrapper/params.py", line 92, in as_pairs
    if f.default == value and f.init:
  File "/data/encoder/virtualenv/lib/python3.6/site-packages/fffw/graph/meta.py", line 104, in wrapper
    return cast(BinaryOp, func)(self, TS(value))  # noqa
  File "/data/encoder/virtualenv/lib/python3.6/site-packages/fffw/graph/meta.py", line 144, in __new__
    return super().__new__(cls, value)  # type: ignore
TypeError: float() argument must be a string or a number, not '_MISSING_TYPE'
```

There should be a MISSING check


Discussion:

Patch:
diff --git a/fffw/encoding/ffmpeg.py b/fffw/encoding/ffmpeg.py
index 23a5790..248f3ef 100644
--- a/fffw/encoding/ffmpeg.py
+++ b/fffw/encoding/ffmpeg.py
@@ -1,8 +1,9 @@
 from dataclasses import dataclass
-from typing import List, Optional, Union, Tuple
+from typing import List, Optional, Union
 
+from fffw.encoding import inputs
 from fffw.encoding.complex import FilterComplex
-from fffw.encoding.inputs import InputList, Input, Stream
+from fffw.encoding.inputs import InputList, Input
 from fffw.encoding.outputs import OutputList, Output, Codec
 from fffw.graph import base, meta
 from fffw.graph.meta import AUDIO, VIDEO, StreamType
@@ -96,20 +97,6 @@ class FFMPEG(BaseWrapper):
             return NotImplemented
         return self.add_output(other)
 
-    @property
-    def inputs(self) -> Tuple[Input, ...]:
-        """
-        :return: a copy of ffmpeg input list.
-        """
-        return tuple(self.__inputs)
-
-    @property
-    def outputs(self) -> Tuple[Output, ...]:
-        """
-        :return: a copy of ffmpeg output list.
-        """
-        return tuple(self.__outputs)
-
     @property
     def filter_device(self) -> meta.Device:
         """ Returns filter hardware device metadata."""
@@ -120,7 +107,7 @@ class FFMPEG(BaseWrapper):
         return meta.Device(hardware, name)
 
     @property
-    def video(self) -> Stream:
+    def video(self) -> inputs.Stream:
         """
         :returns: first video stream not yet connected to filter graph or codec.
 
@@ -133,7 +120,7 @@ class FFMPEG(BaseWrapper):
         return self._get_free_source(VIDEO)
 
     @property
-    def audio(self) -> Stream:
+    def audio(self) -> inputs.Stream:
         """
 
         :returns: first audio stream not yet connected to filter graph or codec.
@@ -147,7 +134,7 @@ class FFMPEG(BaseWrapper):
         """
         return self._get_free_source(AUDIO)
 
-    def _get_free_source(self, kind: StreamType) -> Stream:
+    def _get_free_source(self, kind: StreamType) -> inputs.Stream:
         """
         :param kind: stream type
         :return: first stream of this kind not connected to destination
diff --git a/fffw/wrapper/params.py b/fffw/wrapper/params.py
index 11dc74a..a509f88 100644
--- a/fffw/wrapper/params.py
+++ b/fffw/wrapper/params.py
@@ -1,4 +1,4 @@
-from dataclasses import field, dataclass, Field, fields
+from dataclasses import field, dataclass, Field, fields, MISSING
 from typing import Any, Optional, Tuple, cast, List, Callable
 
 
@@ -89,7 +89,7 @@ class Params:
         for f in self._fields:  # type: Field
             key = f.name
             value = getattr(self, key)
-            if f.default == value and f.init:
+            if f.default is not MISSING and f.default == value and f.init:
                 # if field value has default value and is configurable via
                 # __init__, we omit this field
                 continue


Test patch:
diff --git a/tests/test_ffmpeg.py b/tests/test_ffmpeg.py
index de0195a..d9ac3c4 100644
--- a/tests/test_ffmpeg.py
+++ b/tests/test_ffmpeg.py
@@ -119,23 +119,6 @@ class FFMPEGTestCase(BaseTestCase):
             '/tmp/out.mp3'
         )
 
-    def test_inputs_property(self):
-        """
-        ffmpeg private input list could be accessed via property
-        """
-        self.ffmpeg < self.source
-
-        self.assertTupleEqual(self.ffmpeg.inputs, (self.source,))
-
-    def test_outputs_property(self):
-        """
-        ffmpeg private output list could be accessed via property
-        """
-        self.ffmpeg < self.source
-        self.ffmpeg > self.output
-
-        self.assertTupleEqual(self.ffmpeg.outputs, (self.output,))
-
     def test_filter_device_helper(self):
         """
         filter_device correcly parses init_hardware and filter_hardware flags.
diff --git a/tests/test_wrapper.py b/tests/test_wrapper.py
index ba68852..d6e4ea4 100644
--- a/tests/test_wrapper.py
+++ b/tests/test_wrapper.py
@@ -7,8 +7,10 @@ from dataclasses import dataclass
 from typing import List
 from unittest import TestCase, mock
 
+from fffw.graph import TS
 from fffw.wrapper import param
 from fffw.wrapper.base import UniversalLineReader, BaseWrapper
+from fffw.wrapper.params import Params
 
 
 @dataclass
@@ -119,3 +121,20 @@ class UniversalLineReaderTestCase(TestCase):
 
         with self.assertRaises(asyncio.LimitOverrunError):
             self.assert_lines(lines)
+
+
+@dataclass
+class Wrapper(Params):
+    field: TS  # no default and TS instance
+
+
+class ParamsTestCase(TestCase):
+    """ Check command line parameters rendering."""
+
+    def test_as_pairs_if_default_is_missing(self):
+        """
+        Checks that missing default does not checks value for equality with
+        dataclasses.MISSING.
+        """
+        w = Wrapper(TS(42.0))
+        self.assertEqual(w.as_pairs(), [('field', '42.0')])