Repository: falconry/falcon
Pull request #1804 (2020-12-12 20:19:41)

refactor: rename invoke_coroutine_sync() to async_to_sync()

codecov[bot]: # [Codecov](https://codecov.io/gh/falconry/falcon/pull/1804?src=pr&el=h1) Report
> Merging [#1804](https://codecov.io/gh/falconry/falcon/pull/1804?src=pr&el=desc) (7b323b1) into [master](https://codecov.io/gh/falconry/falcon/commit/9f5d1107e4ba6024b266d5214f4fb4eb7a15e494?el=desc) (9f5d110) will **decrease** coverage by `38.10%`.
> The diff coverage is `66.66%`.

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

```diff
@@             Coverage Diff              @@
##            master    #1804       +/-   ##
============================================
- Coverage   100.00%   61.89%   -38.11%     
============================================
  Files           54       54               
  Lines         5076     5076               
  Branches       808      808               
============================================
- Hits          5076     3142     -1934     
- Misses           0     1924     +1924     
- Partials         0       10       +10     
```


| [Impacted Files](https://codecov.io/gh/falconry/falcon/pull/1804?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [falcon/media/json.py](https://codecov.io/gh/falconry/falcon/pull/1804/diff?src=pr&el=tree#diff-ZmFsY29uL21lZGlhL2pzb24ucHk=) | `65.78% <ø> (-34.22%)` | :arrow_down: |
| [falcon/testing/client.py](https://codecov.io/gh/falconry/falcon/pull/1804/diff?src=pr&el=tree#diff-ZmFsY29uL3Rlc3RpbmcvY2xpZW50LnB5) | `63.09% <0.00%> (-36.91%)` | :arrow_down: |
| [falcon/util/sync.py](https://codecov.io/gh/falconry/falcon/pull/1804/diff?src=pr&el=tree#diff-ZmFsY29uL3V0aWwvc3luYy5weQ==) | `71.11% <100.00%> (-28.89%)` | :arrow_down: |
| [falcon/asgi/ws.py](https://codecov.io/gh/falconry/falcon/pull/1804/diff?src=pr&el=tree#diff-ZmFsY29uL2FzZ2kvd3MucHk=) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [falcon/asgi/app.py](https://codecov.io/gh/falconry/falcon/pull/1804/diff?src=pr&el=tree#diff-ZmFsY29uL2FzZ2kvYXBwLnB5) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [falcon/asgi/reader.py](https://codecov.io/gh/falconry/falcon/pull/1804/diff?src=pr&el=tree#diff-ZmFsY29uL2FzZ2kvcmVhZGVyLnB5) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [falcon/asgi/stream.py](https://codecov.io/gh/falconry/falcon/pull/1804/diff?src=pr&el=tree#diff-ZmFsY29uL2FzZ2kvc3RyZWFtLnB5) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [falcon/asgi/request.py](https://codecov.io/gh/falconry/falcon/pull/1804/diff?src=pr&el=tree#diff-ZmFsY29uL2FzZ2kvcmVxdWVzdC5weQ==) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [falcon/asgi/response.py](https://codecov.io/gh/falconry/falcon/pull/1804/diff?src=pr&el=tree#diff-ZmFsY29uL2FzZ2kvcmVzcG9uc2UucHk=) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [falcon/asgi/multipart.py](https://codecov.io/gh/falconry/falcon/pull/1804/diff?src=pr&el=tree#diff-ZmFsY29uL2FzZ2kvbXVsdGlwYXJ0LnB5) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| ... and [28 more](https://codecov.io/gh/falconry/falcon/pull/1804/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/falconry/falcon/pull/1804?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/1804?src=pr&el=footer). Last update [9f5d110...7b323b1](https://codecov.io/gh/falconry/falcon/pull/1804?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/docs/api/util.rst b/docs/api/util.rst
index 6930c08..c9c7a5b 100644
--- a/docs/api/util.rst
+++ b/docs/api/util.rst
@@ -53,7 +53,7 @@ These functions help traverse the barrier between sync and async code.
 .. autofunction:: falcon.sync_to_async
 .. autofunction:: falcon.wrap_sync_to_async
 .. autofunction:: falcon.wrap_sync_to_async_unsafe
-.. autofunction:: falcon.invoke_coroutine_sync
+.. autofunction:: falcon.async_to_sync
 .. autofunction:: falcon.runs_sync
 
 Other
diff --git a/falcon/media/json.py b/falcon/media/json.py
index de19ee6..cb06ad3 100644
--- a/falcon/media/json.py
+++ b/falcon/media/json.py
@@ -44,7 +44,7 @@ class JSONHandler(BaseHandler):
     If you override the ``dumps`` function, you will need to explicitly set
     ``ensure_ascii`` to ``False`` in order to enable the serialization of
     Unicode characters to UTF-8. This is easily done by using
-    ``functools.partial`` to apply the desired keyword argument. In fact, you
+    :any:`functools.partial` to apply the desired keyword argument. In fact, you
     can use this same technique to customize any option supported by the
     ``dumps`` and ``loads`` functions::
 
@@ -147,7 +147,7 @@ class JSONHandlerWS(TextBaseHandlerWS):
     If you override the ``dumps`` function, you will need to explicitly set
     ``ensure_ascii`` to ``False`` in order to enable the serialization of
     Unicode characters to UTF-8. This is easily done by using
-    ``functools.partial`` to apply the desired keyword argument. In fact, you
+    :any:`functools.partial` to apply the desired keyword argument. In fact, you
     can use this same technique to customize any option supported by the
     ``dumps`` and ``loads`` functions::
 
diff --git a/falcon/util/sync.py b/falcon/util/sync.py
index d7a41b2..12b28c7 100644
--- a/falcon/util/sync.py
+++ b/falcon/util/sync.py
@@ -7,9 +7,9 @@ from typing import Callable
 
 
 __all__ = [
+    'async_to_sync',
     'create_task',
     'get_running_loop',
-    'invoke_coroutine_sync',
     'runs_sync',
     'sync_to_async',
     'wrap_sync_to_async',
@@ -193,16 +193,20 @@ def _wrap_non_coroutine_unsafe(func):
     return wrap_sync_to_async_unsafe(func)
 
 
-def invoke_coroutine_sync(coroutine, *args, **kwargs):
-    """Invoke a coroutine function from a synchronous caller and runs until complete.
+def async_to_sync(coroutine, *args, **kwargs):
+    """Invoke a coroutine function from a synchronous caller.
+
+    This method can be used to invoke an asynchronous task from a synchronous
+    context. The coroutine will be scheduled to run on the current event
+    loop for the current OS thread. If an event loop is not already running,
+    one will be created.
 
     Warning:
-        This method is very inefficient and should only be used
-        for testing purposes. It will create an event loop for the current
-        thread if one is not already running.
+        This method is very inefficient and is intended primarily for testing
+        and prototyping.
 
     Additional arguments not mentioned below are bound to the given
-    coroutine function via ``functools.partial()``.
+    coroutine function via :any:`functools.partial`.
 
     Args:
         coroutine: A coroutine function to invoke.
@@ -222,7 +226,7 @@ def runs_sync(coroutine):
     """Transform a coroutine function into a synchronous method.
 
     This is achieved by always invoking the decorated coroutine function via
-    :meth:`invoke_coroutine_sync`.
+    :meth:`async_to_sync`.
 
     Warning:
         This decorator is very inefficient and should only be used for adapting
@@ -240,6 +244,6 @@ def runs_sync(coroutine):
     """
     @wraps(coroutine)
     def invoke(*args, **kwargs):
-        return invoke_coroutine_sync(coroutine, *args, **kwargs)
+        return async_to_sync(coroutine, *args, **kwargs)
 
     return invoke


Test patch:
diff --git a/falcon/testing/client.py b/falcon/testing/client.py
index 6d34e90..741a8eb 100644
--- a/falcon/testing/client.py
+++ b/falcon/testing/client.py
@@ -32,13 +32,13 @@ from falcon.errors import CompatibilityError
 from falcon.testing import helpers
 from falcon.testing.srmock import StartResponseMock
 from falcon.util import (
+    async_to_sync,
     CaseInsensitiveDict,
     code_to_http_status,
     create_task,
     get_running_loop,
     http_cookies,
     http_date_to_dt,
-    invoke_coroutine_sync,
     json as util_json,
     to_query_str,
 )
@@ -510,7 +510,7 @@ def simulate_request(app, method='GET', path='/', query_string=None,
     """
 
     if _is_asgi_app(app):
-        return invoke_coroutine_sync(
+        return async_to_sync(
             _simulate_request_asgi,
 
             app,
@@ -853,7 +853,7 @@ class ASGIConductor:
 
         As a workaround, the test can be adapted by wrapping it in
         an inline async function and then invoking it via
-        :meth:`falcon.invoke_coroutine_sync` or decorating the test function
+        :meth:`falcon.async_to_sync` or decorating the test function
         with :meth:`falcon.runs_sync`.
 
         Alternatively, you can try searching PyPI to see if an async plugin is
diff --git a/tests/asgi/test_boundedstream_asgi.py b/tests/asgi/test_boundedstream_asgi.py
index f70dec8..52788f0 100644
--- a/tests/asgi/test_boundedstream_asgi.py
+++ b/tests/asgi/test_boundedstream_asgi.py
@@ -114,7 +114,7 @@ def test_read_all(body, extra_body, set_content_length):
         assert s.closed
 
     for t in (test_iteration, test_readall_a, test_readall_b, test_readall_c, test_readall_d):
-        falcon.invoke_coroutine_sync(t)
+        falcon.async_to_sync(t)
 
 
 def test_filelike():
@@ -151,7 +151,7 @@ def test_filelike():
             async for chunk in s:
                 pass
 
-    falcon.invoke_coroutine_sync(test_iteration)
+    falcon.async_to_sync(test_iteration)
 
 
 @pytest.mark.parametrize('body', [
@@ -210,8 +210,8 @@ def test_read_chunks(body, chunk_size):
         assert b''.join(chunks) == body
 
     for t in (test_nonmixed, test_mixed_a, test_mixed_b, test_mixed_iter):
-        falcon.invoke_coroutine_sync(t)
-        falcon.invoke_coroutine_sync(t)
+        falcon.async_to_sync(t)
+        falcon.async_to_sync(t)
 
 
 def test_exhaust_with_disconnect():
@@ -232,7 +232,7 @@ def test_exhaust_with_disconnect():
         assert await s.read(100) == b''
         assert s.eof
 
-    falcon.invoke_coroutine_sync(t)
+    falcon.async_to_sync(t)
 
 
 @falcon.runs_sync
@@ -273,7 +273,7 @@ def test_iteration_already_started():
 
         assert b''.join(chunks) == body
 
-    falcon.invoke_coroutine_sync(t)
+    falcon.async_to_sync(t)
 
 
 def _stream(body, content_length=None):
diff --git a/tests/asgi/test_buffered_reader.py b/tests/asgi/test_buffered_reader.py
index 8f345cc..0377971 100644
--- a/tests/asgi/test_buffered_reader.py
+++ b/tests/asgi/test_buffered_reader.py
@@ -21,7 +21,7 @@ def async_take(source, count=None):
                 return result
         return result
 
-    return falcon.invoke_coroutine_sync(collect)
+    return falcon.async_to_sync(collect)
 
 
 async def chop_data(data, min_size=1024, max_size=64 * 1024):
diff --git a/tests/asgi/test_lifespan_handlers.py b/tests/asgi/test_lifespan_handlers.py
index fbd2934..fa306c3 100644
--- a/tests/asgi/test_lifespan_handlers.py
+++ b/tests/asgi/test_lifespan_handlers.py
@@ -231,6 +231,6 @@ def test_asgi_conductor_raised_error_skips_shutdown():
             async with testing.ASGIConductor(app):
                 raise SomeException()
 
-    falcon.invoke_coroutine_sync(t)
+    falcon.async_to_sync(t)
     assert foo.called_startup
     assert not foo.called_shutdown
diff --git a/tests/asgi/test_scope.py b/tests/asgi/test_scope.py
index d422870..8ee4ff4 100644
--- a/tests/asgi/test_scope.py
+++ b/tests/asgi/test_scope.py
@@ -113,7 +113,7 @@ def test_lifespan_scope_default_version():
 
         await t
 
-    falcon.invoke_coroutine_sync(t)
+    falcon.async_to_sync(t)
 
     assert not resource.called
 
@@ -145,7 +145,7 @@ def test_lifespan_scope_version(spec_version, supported):
 
     if not supported:
         with pytest.raises(UnsupportedScopeError):
-            falcon.invoke_coroutine_sync(
+            falcon.async_to_sync(
                 app.__call__, scope, req_event_emitter, resp_event_collector
             )
 
@@ -164,7 +164,7 @@ def test_lifespan_scope_version(spec_version, supported):
 
         await t
 
-    falcon.invoke_coroutine_sync(t)
+    falcon.async_to_sync(t)
 
 
 def test_query_string_values():
@@ -230,7 +230,7 @@ def _call_with_scope(scope):
     req_event_emitter = testing.ASGIRequestEventEmitter()
     resp_event_collector = testing.ASGIResponseEventCollector()
 
-    falcon.invoke_coroutine_sync(
+    falcon.async_to_sync(
         app.__call__, scope, req_event_emitter, resp_event_collector
     )
 
diff --git a/tests/asgi/test_sse.py b/tests/asgi/test_sse.py
index 97fd467..9734766 100644
--- a/tests/asgi/test_sse.py
+++ b/tests/asgi/test_sse.py
@@ -138,7 +138,7 @@ def test_multiple_events():
                 assert event_count == 6
                 assert result_text == expected_result_text
 
-    falcon.invoke_coroutine_sync(_test)
+    falcon.async_to_sync(_test)
 
 
 def test_multiple_events_early_disconnect():
@@ -179,7 +179,7 @@ def test_multiple_events_early_disconnect():
                 assert result_text.startswith('data: whassup\n\n' * 5)
                 assert event_count == 5
 
-    falcon.invoke_coroutine_sync(_test)
+    falcon.async_to_sync(_test)
 
 
 def test_invalid_event_values():
diff --git a/tests/test_after_hooks.py b/tests/test_after_hooks.py
index 1d26b37..13f7209 100644
--- a/tests/test_after_hooks.py
+++ b/tests/test_after_hooks.py
@@ -297,7 +297,7 @@ def test_resource_with_uri_fields_async():
         await resource.on_get(req, resp, '1', '2')
         assert resource.fields == ('1', '2')
 
-    falcon.invoke_coroutine_sync(test_direct)
+    falcon.async_to_sync(test_direct)
 
 
 @pytest.mark.parametrize(
diff --git a/tests/test_before_hooks.py b/tests/test_before_hooks.py
index 7376f6f..7ca57a0 100644
--- a/tests/test_before_hooks.py
+++ b/tests/test_before_hooks.py
@@ -385,7 +385,7 @@ def test_parser_async(body, doc):
         await resource.on_get(req, resp, doc)
         assert resource.doc == doc
 
-    falcon.invoke_coroutine_sync(test_direct)
+    falcon.async_to_sync(test_direct)
 
 
 def test_wrapped_resource(client, wrapped_resource):
diff --git a/tests/test_media_handlers.py b/tests/test_media_handlers.py
index 1bf62bc..b4c9ea8 100644
--- a/tests/test_media_handlers.py
+++ b/tests/test_media_handlers.py
@@ -78,7 +78,7 @@ def test_serialization(asgi, func, body, expected):
     args = (body, b'application/javacript')
 
     if asgi:
-        result = falcon.invoke_coroutine_sync(handler.serialize_async, *args)
+        result = falcon.async_to_sync(handler.serialize_async, *args)
     else:
         result = handler.serialize(*args)
 
@@ -102,7 +102,7 @@ def test_deserialization(asgi, func, body, expected):
         s = BoundedStream(testing.ASGIRequestEventEmitter(body))
         args.insert(0, s)
 
-        result = falcon.invoke_coroutine_sync(handler.deserialize_async, *args)
+        result = falcon.async_to_sync(handler.deserialize_async, *args)
     else:
         args.insert(0, io.BytesIO(body))
         result = handler.deserialize(*args)
diff --git a/tests/test_media_urlencoded.py b/tests/test_media_urlencoded.py
index c5c2125..04de691 100644
--- a/tests/test_media_urlencoded.py
+++ b/tests/test_media_urlencoded.py
@@ -30,7 +30,7 @@ def test_urlencoded_form_handler_serialize(data, expected):
     handler = media.URLEncodedFormHandler()
     assert handler.serialize(data, falcon.MEDIA_URLENCODED) == expected
 
-    value = falcon.invoke_coroutine_sync(handler.serialize_async, data, falcon.MEDIA_URLENCODED)
+    value = falcon.async_to_sync(handler.serialize_async, data, falcon.MEDIA_URLENCODED)
     assert value == expected
 
 
diff --git a/tests/test_static.py b/tests/test_static.py
index a534842..a37beeb 100644
--- a/tests/test_static.py
+++ b/tests/test_static.py
@@ -102,7 +102,7 @@ def test_bad_path(asgi, uri, monkeypatch):
 
     with pytest.raises(falcon.HTTPNotFound):
         if asgi:
-            falcon.invoke_coroutine_sync(sr, req, resp)
+            falcon.async_to_sync(sr, req, resp)
         else:
             sr(req, resp)
 
@@ -181,7 +181,7 @@ def test_good_path(asgi, uri_prefix, uri_path, expected_path, mtype, monkeypatch
             await sr(req, resp)
             return await resp.stream.read()
 
-        body = falcon.invoke_coroutine_sync(run)
+        body = falcon.async_to_sync(run)
     else:
         sr(req, resp)
         body = resp.stream.read()
@@ -291,7 +291,7 @@ def test_fallback_filename(asgi, uri, default, expected, content_type, downloada
             await sr(req, resp)
             return await resp.stream.read()
 
-        body = falcon.invoke_coroutine_sync(run)
+        body = falcon.async_to_sync(run)
     else:
         sr(req, resp)
         body = resp.stream.read()
diff --git a/tests/test_validators.py b/tests/test_validators.py
index c330deb..c2eea14 100644
--- a/tests/test_validators.py
+++ b/tests/test_validators.py
@@ -121,7 +121,7 @@ def call_method(asgi, method_name, *args):
     resource = ResourceAsync() if asgi else Resource()
 
     if asgi:
-        return falcon.invoke_coroutine_sync(getattr(resource, method_name), *args)
+        return falcon.async_to_sync(getattr(resource, method_name), *args)
 
     return getattr(resource, method_name)(*args)