Repository: XLSForm/pyxform
Pull request #483 (2020-11-18 14:44:04)

Add quality attribute for audio questions
This adds support for the `quality` attribute detailed [here](https://forum.getodk.org/t/form-spec-proposal-configure-audio-quality-for-internal-recording/).

#### Why is this the best possible solution? Were any other approaches considered?

Didn't seem like there were a lot of different ways to go but definitely keen to hear criticism as I'm pretty new to the codebase.

#### What are the regression risks?

Only audio questions have been changed so that's the only area where something could go wrong (I'd imagine).

#### Does this change require updates to documentation? If so, please file an issue [here](https://github.com/XLSForm/xlsform.github.io) and include the link below.

Yes but that's been planned separately!

#### Before submitting this PR, please make sure you have:
- [x] included test cases for core behavior and edge cases in `tests_v1`
- [x] run `nosetests` and verified all tests pass
- [x] run `black pyxform` to format code
- [x] verified that any code or assets from external sources are properly credited in comments


Patch:
diff --git a/README.rst b/README.rst
index f728769..98dc982 100644
--- a/README.rst
+++ b/README.rst
@@ -90,13 +90,16 @@ You can run tests with::
 
     nosetests
 
-
 Before committing, make sure to format your code using `black`::
 
     black pyxform
 
 If you are using a copy of black outside your virtualenv, make sure it is the same version as listed in requirements.pip.
 
+Writing tests
+-------------
+Make sure to include tests for the changes you're working on. When writing new tests you should add them in `pyxform/test_v1` instead of in `pyxform/test` (which contains tests using an older style). Generally, the easiest way to write tests is to extend `PyxformTestCase` which will let you compile example XLSForm and make assertions on the resulting XForm.
+
 Documentation
 =============
 To check out the documentation for pyxform do the following::
@@ -124,7 +127,7 @@ Releasing pyxform
 5. Update ``CHANGES.txt`` with the text of the draft release.
 6. Update ``README.rst``, ``setup.py``, ``pyxform/__init__.py`` with the new release version number.
 7. Commit, push the branch, and initiate a pull request. Wait for tests to pass, then merge the PR.
-8. In a clean new release only directory, checkout master. 
+8. In a clean new release only directory, checkout master.
 9. Create a new virtualenv in this directory to ensure a clean Python environment::
 
      mkvirtualenv pyxform-release
diff --git a/pyxform/xls2json.py b/pyxform/xls2json.py
index d3f0ca3..88c3c23 100644
--- a/pyxform/xls2json.py
+++ b/pyxform/xls2json.py
@@ -1283,6 +1283,25 @@ def workbook_to_json(
             parent_children_array.append(new_dict)
             continue
 
+        if question_type == "audio":
+            new_dict = row.copy()
+            parameters = get_parameters(row.get("parameters", ""), ["quality"])
+
+            if "quality" in parameters.keys():
+                if parameters["quality"] not in [
+                    "voice-only",
+                    "low",
+                    "normal",
+                    "external",
+                ]:
+                    raise PyXFormError("Invalid value for quality.")
+
+                new_dict["bind"] = new_dict.get("bind", {})
+                new_dict["bind"].update({"odk:quality": parameters["quality"]})
+
+            parent_children_array.append(new_dict)
+            continue
+
         # TODO: Consider adding some question_type validation here.
 
         # Put the row in the json dict as is:


Test patch:
diff --git a/pyxform/tests_v1/test_audio_quality.py b/pyxform/tests_v1/test_audio_quality.py
new file mode 100644
index 0000000..d8240fc
--- /dev/null
+++ b/pyxform/tests_v1/test_audio_quality.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+from pyxform.tests_v1.pyxform_test_case import PyxformTestCase
+
+
+class AudioQualityTest(PyxformTestCase):
+    def test_voice_only(self):
+        self.assertPyxformXform(
+            name="data",
+            md="""
+            | survey |        |          |       |                |
+            |        | type   | name     | label | parameters     |
+            |        | audio  | audio    | Audio | quality=voice-only |
+            """,
+            xml__contains=[
+                'xmlns:orx="http://openrosa.org/xforms"',
+                '<bind nodeset="/data/audio" type="binary" odk:quality="voice-only"/>',
+            ],
+        )
+
+    def test_low(self):
+        self.assertPyxformXform(
+            name="data",
+            md="""
+            | survey |        |          |       |                |
+            |        | type   | name     | label | parameters     |
+            |        | audio  | audio    | Audio | quality=low |
+            """,
+            xml__contains=[
+                'xmlns:orx="http://openrosa.org/xforms"',
+                '<bind nodeset="/data/audio" type="binary" odk:quality="low"/>',
+            ],
+        )
+
+    def test_normal(self):
+        self.assertPyxformXform(
+            name="data",
+            md="""
+            | survey |        |          |       |                |
+            |        | type   | name     | label | parameters     |
+            |        | audio  | audio    | Audio | quality=normal |
+            """,
+            xml__contains=[
+                'xmlns:orx="http://openrosa.org/xforms"',
+                '<bind nodeset="/data/audio" type="binary" odk:quality="normal"/>',
+            ],
+        )
+
+    def test_external(self):
+        self.assertPyxformXform(
+            name="data",
+            md="""
+            | survey |        |          |       |                |
+            |        | type   | name     | label | parameters     |
+            |        | audio  | audio    | Audio | quality=external |
+            """,
+            xml__contains=[
+                'xmlns:orx="http://openrosa.org/xforms"',
+                '<bind nodeset="/data/audio" type="binary" odk:quality="external"/>',
+            ],
+        )
+
+    def test_foo_fails(self):
+        self.assertPyxformXform(
+            name="data",
+            md="""
+            | survey |        |          |       |                |
+            |        | type   | name     | label | parameters     |
+            |        | audio  | audio    | Audio | quality=foo |
+            """,
+            errored=True,
+            error__contains=["Invalid value for quality."],
+        )