Repository: falconry/falcon
Pull request #1606 (2019-11-10 02:28:56)

feat(Request): Support single-char truthy strings
# Summary of Changes

Add support to `Request.get_param_as_bool()` for using ``'t'`` and ``'y'``
values for ``True``, as well as ``'f'`` and ``'n'`` for ``False``.


# Related Issues

N/A

# Pull Request Checklist

This is just a reminder about the most common mistakes.  Please make sure that you tick all *appropriate* boxes.  But please read our [contribution guide](https://github.com/falconry/falcon/blob/master/CONTRIBUTING.md) at least once; it will save you a few review cycles!

If an item doesn't apply to your pull request, **check it anyway** to make it apparent that there's nothing to do.

- [x] Added **tests** for changed code.
- [x] Prefixed code comments with GitHub nick and an appropriate prefix.
- [x] Coding style is consistent with the rest of the framework.
- [x] Updated **documentation** for changed code.
    - [ ] Added docstrings for any new classes, functions, or modules.
    - [ ] Updated docstrings for any modifications to existing code.
    - [ ] Added references to new classes, functions, or modules to the relevant RST file under `docs/`.
    - [ ] Updated all relevant supporting documentation files under `docs/`.
    - [ ] A copyright notice is included at the top of any new modules (using your own name or the name of your organization).
    - [ ] Changed/added classes/methods/functions have appropriate `versionadded`, `versionchanged`, or `deprecated` [directives](http://www.sphinx-doc.org/en/stable/usage/restructuredtext/directives.html?highlight=versionadded#directive-versionadded).
- [x] Changes (and possible deprecations) have [towncrier](https://pypi.org/project/towncrier/) news fragments under `docs/_newsfragments/`. (Run `towncrier --draft` to ensure it renders correctly.)

If you have *any* questions to *any* of the points above, just **submit and ask**! This checklist is here to *help* you, not to deter you from contributing!

*PR template inspired by the attrs project.*


codecov[bot]: # [Codecov](https://codecov.io/gh/falconry/falcon/pull/1606?src=pr&el=h1) Report
> Merging [#1606](https://codecov.io/gh/falconry/falcon/pull/1606?src=pr&el=desc) into [master](https://codecov.io/gh/falconry/falcon/commit/4df7706eb69f2064ee00a4d5280505d893f27638?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/falconry/falcon/pull/1606/graphs/tree.svg?width=650&token=zOtkfNQ3h6&height=150&src=pr)](https://codecov.io/gh/falconry/falcon/pull/1606?src=pr&el=tree)

```diff
@@          Coverage Diff           @@
##           master   #1606   +/-   ##
======================================
  Coverage     100%    100%           
======================================
  Files          40      40           
  Lines        2595    2595           
  Branches      394     394           
======================================
  Hits         2595    2595
```


| [Impacted Files](https://codecov.io/gh/falconry/falcon/pull/1606?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [falcon/request.py](https://codecov.io/gh/falconry/falcon/pull/1606/diff?src=pr&el=tree#diff-ZmFsY29uL3JlcXVlc3QucHk=) | `100% <100%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/falconry/falcon/pull/1606?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/falconry/falcon/pull/1606?src=pr&el=footer). Last update [4df7706...fe2627a](https://codecov.io/gh/falconry/falcon/pull/1606?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/docs/_newsfragments/support-additional-truthy-param-values.feature.rst b/docs/_newsfragments/support-additional-truthy-param-values.feature.rst
new file mode 100644
index 0000000..4e3e8fc
--- /dev/null
+++ b/docs/_newsfragments/support-additional-truthy-param-values.feature.rst
@@ -0,0 +1,2 @@
+:meth:`~.Request.get_param_as_bool` now supports the use of ``'t'`` and ``'y'``
+values for ``True``, as well as ``'f'`` and ``'n'`` for ``False``.
diff --git a/falcon/request.py b/falcon/request.py
index 3fdce88..aa27d69 100644
--- a/falcon/request.py
+++ b/falcon/request.py
@@ -30,8 +30,8 @@ from falcon.vendor import mimeparse
 DEFAULT_ERROR_LOG_FORMAT = ('{0:%Y-%m-%d %H:%M:%S} [FALCON] [ERROR]'
                             ' {1} {2}{3} => ')
 
-TRUE_STRINGS = frozenset(['true', 'True', 'yes', '1', 'on'])
-FALSE_STRINGS = frozenset(['false', 'False', 'no', '0', 'off'])
+TRUE_STRINGS = frozenset(['true', 'True', 't', 'yes', 'y', '1', 'on'])
+FALSE_STRINGS = frozenset(['false', 'False', 'f', 'no', 'n', '0', 'off'])
 WSGI_CONTENT_HEADERS = frozenset(['CONTENT_TYPE', 'CONTENT_LENGTH'])
 
 # PERF(kgriffs): Avoid an extra namespace lookup when using these functions
@@ -1434,8 +1434,8 @@ class Request:
 
         The following boolean strings are supported::
 
-            TRUE_STRINGS = ('true', 'True', 'yes', '1', 'on')
-            FALSE_STRINGS = ('false', 'False', 'no', '0', 'off')
+            TRUE_STRINGS = ('true', 'True', 't', 'yes', 'y', '1', 'on')
+            FALSE_STRINGS = ('false', 'False', 'f', 'no', 'n', '0', 'off')
 
         Args:
             name (str): Parameter name, case-sensitive (e.g., 'detailed').


Test patch:
diff --git a/tests/test_query_params.py b/tests/test_query_params.py
index c31668f..744c856 100644
--- a/tests/test_query_params.py
+++ b/tests/test_query_params.py
@@ -450,9 +450,25 @@ class TestQueryParams:
     def test_boolean(self, simulate_request, client, resource):
         client.app.add_route('/', resource)
         client.app.req_options.keep_blank_qs_values = False
-        query_string = ('echo=true&doit=false&bogus=bar&bogus2=foo&'
-                        't1=True&f1=False&t2=yes&f2=no&blank&one=1&zero=0&'
-                        'checkbox1=on&checkbox2=off')
+        query_string = '&'.join([
+            'echo=true',
+            'doit=false',
+            'bogus=bar',
+            'bogus2=foo',
+            't1=True',
+            'f1=False',
+            't2=yes',
+            'f2=no',
+            't3=y',
+            'f3=n',
+            't4=t',
+            'f4=f',
+            'blank',
+            'one=1',
+            'zero=0',
+            'checkbox1=on',
+            'checkbox2=off',
+        ])
         simulate_request(client=client, path='/', query_string=query_string)
 
         req = resource.captured_req
@@ -472,10 +488,10 @@ class TestQueryParams:
         assert req.get_param_as_bool('echo') is True
         assert req.get_param_as_bool('doit') is False
 
-        assert req.get_param_as_bool('t1') is True
-        assert req.get_param_as_bool('t2') is True
-        assert req.get_param_as_bool('f1') is False
-        assert req.get_param_as_bool('f2') is False
+        for i in range(1, 5):
+            assert req.get_param_as_bool('t' + str(i)) is True
+            assert req.get_param_as_bool('f' + str(i)) is False
+
         assert req.get_param_as_bool('one') is True
         assert req.get_param_as_bool('zero') is False
         assert req.get_param('blank') is None