Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-asgiref for openSUSE:Factory 
checked in at 2021-01-18 11:27:13
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-asgiref (Old)
 and      /work/SRC/openSUSE:Factory/.python-asgiref.new.28504 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-asgiref"

Mon Jan 18 11:27:13 2021 rev:3 rq:863007 version:3.3.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-asgiref/python-asgiref.changes    
2020-07-02 23:54:49.800572868 +0200
+++ /work/SRC/openSUSE:Factory/.python-asgiref.new.28504/python-asgiref.changes 
2021-01-18 11:30:28.096340415 +0100
@@ -1,0 +2,10 @@
+Thu Jan 14 04:31:06 UTC 2021 - Steve Kowalik <[email protected]>
+
+- Update to 3.3.1
+  * Updated StatelessServer to use ASGI v3 single-callable applications.
+  * sync_to_async now defaults to thread-sensitive mode being on
+  * async_to_sync now works inside of forked processes
+  * WsgiToAsgi now correctly clamps its response body when Content-Length
+    is set 
+
+-------------------------------------------------------------------

Old:
----
  asgiref-3.2.10.tar.gz

New:
----
  asgiref-3.3.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-asgiref.spec ++++++
--- /var/tmp/diff_new_pack.ZIWFMI/_old  2021-01-18 11:30:28.760340790 +0100
+++ /var/tmp/diff_new_pack.ZIWFMI/_new  2021-01-18 11:30:28.764340792 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-asgiref
 #
-# Copyright (c) 2020 SUSE LLC
+# Copyright (c) 2021 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -19,11 +19,10 @@
 %define skip_python2 1
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-asgiref
-Version:        3.2.10
+Version:        3.3.1
 Release:        0
 Summary:        ASGI specs, helper code, and adapters
 License:        BSD-3-Clause
-Group:          Development/Languages/Python
 URL:            https://github.com/django/asgiref/
 Source:         
https://files.pythonhosted.org/packages/source/a/asgiref/asgiref-%{version}.tar.gz
 BuildRequires:  %{python_module base >= 3.5}

++++++ asgiref-3.2.10.tar.gz -> asgiref-3.3.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asgiref-3.2.10/PKG-INFO new/asgiref-3.3.1/PKG-INFO
--- old/asgiref-3.2.10/PKG-INFO 2020-06-18 20:49:06.533756500 +0200
+++ new/asgiref-3.3.1/PKG-INFO  2020-11-09 16:55:38.710000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: asgiref
-Version: 3.2.10
+Version: 3.3.1
 Summary: ASGI specs, helper code, and adapters
 Home-page: https://github.com/django/asgiref/
 Author: Django Software Foundation
@@ -54,7 +54,11 @@
         
         Note that exactly what threads things run in is very specific, and 
aimed to
         keep maximum compatibility with old synchronous code. See
-        "Synchronous code & Threads" below for a full explanation.
+        "Synchronous code & Threads" below for a full explanation. By default,
+        ``sync_to_async`` will run all synchronous code in the program in the 
same
+        thread for safety reasons; you can disable this for more performance 
with
+        ``@sync_to_async(thread_sensitive=False)``, but make sure that your 
code does
+        not rely on anything bound to threads (like database connections) when 
you do.
         
         
         Threadlocal replacement
@@ -177,7 +181,7 @@
         This means you now have two basic states:
         
         * If the outermost layer of your program is synchronous, then all 
async code
-          run through ``AsyncToSync`` will run in a per-call event loop in 
arbitary
+          run through ``AsyncToSync`` will run in a per-call event loop in 
arbitrary
           sub-threads, while all ``thread_sensitive`` code will run in the 
main thread.
         
         * If the outermost layer of your program is asynchronous, then all 
async code
@@ -220,6 +224,7 @@
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
 Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
 Classifier: Topic :: Internet :: WWW/HTTP
 Requires-Python: >=3.5
 Provides-Extra: tests
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asgiref-3.2.10/README.rst 
new/asgiref-3.3.1/README.rst
--- old/asgiref-3.2.10/README.rst       2020-06-18 20:48:49.000000000 +0200
+++ new/asgiref-3.3.1/README.rst        2020-11-09 16:53:04.000000000 +0100
@@ -43,7 +43,11 @@
 
 Note that exactly what threads things run in is very specific, and aimed to
 keep maximum compatibility with old synchronous code. See
-"Synchronous code & Threads" below for a full explanation.
+"Synchronous code & Threads" below for a full explanation. By default,
+``sync_to_async`` will run all synchronous code in the program in the same
+thread for safety reasons; you can disable this for more performance with
+``@sync_to_async(thread_sensitive=False)``, but make sure that your code does
+not rely on anything bound to threads (like database connections) when you do.
 
 
 Threadlocal replacement
@@ -166,7 +170,7 @@
 This means you now have two basic states:
 
 * If the outermost layer of your program is synchronous, then all async code
-  run through ``AsyncToSync`` will run in a per-call event loop in arbitary
+  run through ``AsyncToSync`` will run in a per-call event loop in arbitrary
   sub-threads, while all ``thread_sensitive`` code will run in the main thread.
 
 * If the outermost layer of your program is asynchronous, then all async code
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asgiref-3.2.10/asgiref/__init__.py 
new/asgiref-3.3.1/asgiref/__init__.py
--- old/asgiref-3.2.10/asgiref/__init__.py      2020-06-18 20:48:49.000000000 
+0200
+++ new/asgiref-3.3.1/asgiref/__init__.py       2020-11-09 16:55:08.000000000 
+0100
@@ -1 +1 @@
-__version__ = "3.2.10"
+__version__ = "3.3.1"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asgiref-3.2.10/asgiref/server.py 
new/asgiref-3.3.1/asgiref/server.py
--- old/asgiref-3.2.10/asgiref/server.py        2020-06-18 20:48:49.000000000 
+0200
+++ new/asgiref-3.3.1/asgiref/server.py 2020-11-09 16:53:04.000000000 +0100
@@ -3,6 +3,8 @@
 import time
 import traceback
 
+from .compatibility import guarantee_single_callable
+
 logger = logging.getLogger(__name__)
 
 
@@ -84,10 +86,11 @@
             self.delete_oldest_application_instance()
         # Make an instance of the application
         input_queue = asyncio.Queue()
-        application_instance = self.application(scope=scope)
+        application_instance = guarantee_single_callable(self.application)
         # Run it, and stash the future for later checking
         future = asyncio.ensure_future(
             application_instance(
+                scope=scope,
                 receive=input_queue.get,
                 send=lambda message: self.application_send(scope, message),
             )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asgiref-3.2.10/asgiref/sync.py 
new/asgiref-3.3.1/asgiref/sync.py
--- old/asgiref-3.2.10/asgiref/sync.py  2020-06-18 20:48:49.000000000 +0200
+++ new/asgiref-3.3.1/asgiref/sync.py   2020-11-09 16:53:04.000000000 +0100
@@ -61,9 +61,17 @@
             except RuntimeError:
                 # There's no event loop in this thread. Look for the 
threadlocal if
                 # we're inside SyncToAsync
-                self.main_event_loop = getattr(
-                    SyncToAsync.threadlocal, "main_event_loop", None
+                main_event_loop_pid = getattr(
+                    SyncToAsync.threadlocal, "main_event_loop_pid", None
                 )
+                # We make sure the parent loop is from the same process - if
+                # they've forked, this is not going to be valid any more (#194)
+                if main_event_loop_pid and main_event_loop_pid == os.getpid():
+                    self.main_event_loop = getattr(
+                        SyncToAsync.threadlocal, "main_event_loop", None
+                    )
+                else:
+                    self.main_event_loop = None
 
     def __call__(self, *args, **kwargs):
         # You can't call AsyncToSync from a thread with a running event loop
@@ -198,7 +206,7 @@
             if exc_info[1]:
                 try:
                     raise exc_info[1]
-                except:
+                except Exception:
                     result = await self.awaitable(*args, **kwargs)
             else:
                 result = await self.awaitable(*args, **kwargs)
@@ -247,7 +255,7 @@
     # Single-thread executor for thread-sensitive code
     single_thread_executor = ThreadPoolExecutor(max_workers=1)
 
-    def __init__(self, func, thread_sensitive=False):
+    def __init__(self, func, thread_sensitive=True):
         self.func = func
         functools.update_wrapper(self, func)
         self._thread_sensitive = thread_sensitive
@@ -312,6 +320,7 @@
         """
         # Set the threadlocal for AsyncToSync
         self.threadlocal.main_event_loop = loop
+        self.threadlocal.main_event_loop_pid = os.getpid()
         # Set the task mapping (used for the locals module)
         current_thread = threading.current_thread()
         if AsyncToSync.launch_map.get(source_task) == current_thread:
@@ -328,7 +337,7 @@
             if exc_info[1]:
                 try:
                     raise exc_info[1]
-                except:
+                except Exception:
                     return func(*args, **kwargs)
             else:
                 return func(*args, **kwargs)
@@ -356,6 +365,11 @@
             return None
 
 
-# Lowercase is more sensible for most things
-sync_to_async = SyncToAsync
+# Lowercase aliases (and decorator friendliness)
 async_to_sync = AsyncToSync
+
+
+def sync_to_async(func=None, thread_sensitive=True):
+    if func is None:
+        return lambda f: SyncToAsync(f, thread_sensitive=thread_sensitive)
+    return SyncToAsync(func, thread_sensitive=thread_sensitive)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asgiref-3.2.10/asgiref/wsgi.py 
new/asgiref-3.3.1/asgiref/wsgi.py
--- old/asgiref-3.2.10/asgiref/wsgi.py  2020-06-18 20:48:49.000000000 +0200
+++ new/asgiref-3.3.1/asgiref/wsgi.py   2020-10-09 18:25:45.000000000 +0200
@@ -29,6 +29,7 @@
     def __init__(self, wsgi_application):
         self.wsgi_application = wsgi_application
         self.response_started = False
+        self.response_content_length = None
 
     async def __call__(self, scope, receive, send):
         if scope["type"] != "http":
@@ -55,8 +56,8 @@
         """
         environ = {
             "REQUEST_METHOD": scope["method"],
-            "SCRIPT_NAME": scope.get("root_path", ""),
-            "PATH_INFO": scope["path"],
+            "SCRIPT_NAME": scope.get("root_path", 
"").encode("utf8").decode("latin1"),
+            "PATH_INFO": scope["path"].encode("utf8").decode("latin1"),
             "QUERY_STRING": scope["query_string"].decode("ascii"),
             "SERVER_PROTOCOL": "HTTP/%s" % scope["http_version"],
             "wsgi.version": (1, 0),
@@ -114,6 +115,11 @@
             (name.lower().encode("ascii"), value.encode("ascii"))
             for name, value in response_headers
         ]
+        # Extract content-length
+        self.response_content_length = None
+        for name, value in response_headers:
+            if name.lower() == "content-length":
+                self.response_content_length = int(value)
         # Build and send response start message.
         self.response_start = {
             "type": "http.response.start",
@@ -130,14 +136,25 @@
         # Translate the scope and incoming request body into a WSGI environ
         environ = self.build_environ(self.scope, body)
         # Run the WSGI app
+        bytes_sent = 0
         for output in self.wsgi_application(environ, self.start_response):
             # If this is the first response, include the response headers
             if not self.response_started:
                 self.response_started = True
                 self.sync_send(self.response_start)
+            # If the application supplies a Content-Length header
+            if self.response_content_length is not None:
+                # The server should not transmit more bytes to the client than 
the header allows
+                bytes_allowed = self.response_content_length - bytes_sent
+                if len(output) > bytes_allowed:
+                    output = output[:bytes_allowed]
             self.sync_send(
                 {"type": "http.response.body", "body": output, "more_body": 
True}
             )
+            bytes_sent += len(output)
+            # The server should stop iterating over the response when enough 
data has been sent
+            if bytes_sent == self.response_content_length:
+                break
         # Close connection
         if not self.response_started:
             self.response_started = True
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asgiref-3.2.10/asgiref.egg-info/PKG-INFO 
new/asgiref-3.3.1/asgiref.egg-info/PKG-INFO
--- old/asgiref-3.2.10/asgiref.egg-info/PKG-INFO        2020-06-18 
20:49:06.000000000 +0200
+++ new/asgiref-3.3.1/asgiref.egg-info/PKG-INFO 2020-11-09 16:55:38.000000000 
+0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: asgiref
-Version: 3.2.10
+Version: 3.3.1
 Summary: ASGI specs, helper code, and adapters
 Home-page: https://github.com/django/asgiref/
 Author: Django Software Foundation
@@ -54,7 +54,11 @@
         
         Note that exactly what threads things run in is very specific, and 
aimed to
         keep maximum compatibility with old synchronous code. See
-        "Synchronous code & Threads" below for a full explanation.
+        "Synchronous code & Threads" below for a full explanation. By default,
+        ``sync_to_async`` will run all synchronous code in the program in the 
same
+        thread for safety reasons; you can disable this for more performance 
with
+        ``@sync_to_async(thread_sensitive=False)``, but make sure that your 
code does
+        not rely on anything bound to threads (like database connections) when 
you do.
         
         
         Threadlocal replacement
@@ -177,7 +181,7 @@
         This means you now have two basic states:
         
         * If the outermost layer of your program is synchronous, then all 
async code
-          run through ``AsyncToSync`` will run in a per-call event loop in 
arbitary
+          run through ``AsyncToSync`` will run in a per-call event loop in 
arbitrary
           sub-threads, while all ``thread_sensitive`` code will run in the 
main thread.
         
         * If the outermost layer of your program is asynchronous, then all 
async code
@@ -220,6 +224,7 @@
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
 Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
 Classifier: Topic :: Internet :: WWW/HTTP
 Requires-Python: >=3.5
 Provides-Extra: tests
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asgiref-3.2.10/asgiref.egg-info/SOURCES.txt 
new/asgiref-3.3.1/asgiref.egg-info/SOURCES.txt
--- old/asgiref-3.2.10/asgiref.egg-info/SOURCES.txt     2020-06-18 
20:49:06.000000000 +0200
+++ new/asgiref-3.3.1/asgiref.egg-info/SOURCES.txt      2020-11-09 
16:55:38.000000000 +0100
@@ -20,6 +20,7 @@
 asgiref.egg-info/top_level.txt
 tests/test_compatibility.py
 tests/test_local.py
+tests/test_server.py
 tests/test_sync.py
 tests/test_sync_contextvars.py
 tests/test_testing.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asgiref-3.2.10/setup.cfg new/asgiref-3.3.1/setup.cfg
--- old/asgiref-3.2.10/setup.cfg        2020-06-18 20:49:06.537758600 +0200
+++ new/asgiref-3.3.1/setup.cfg 2020-11-09 16:55:38.710000000 +0100
@@ -20,6 +20,7 @@
        Programming Language :: Python :: 3.6
        Programming Language :: Python :: 3.7
        Programming Language :: Python :: 3.8
+       Programming Language :: Python :: 3.9
        Topic :: Internet :: WWW/HTTP
 project_urls = 
        Documentation = https://asgi.readthedocs.io/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asgiref-3.2.10/tests/test_compatibility.py 
new/asgiref-3.3.1/tests/test_compatibility.py
--- old/asgiref-3.2.10/tests/test_compatibility.py      2020-06-18 
20:48:49.000000000 +0200
+++ new/asgiref-3.3.1/tests/test_compatibility.py       2020-11-09 
16:53:04.000000000 +0100
@@ -67,11 +67,11 @@
     """
     Tests that the signature matcher works as expected.
     """
-    assert is_double_callable(double_application_function) == True
-    assert is_double_callable(DoubleApplicationClass) == True
-    assert is_double_callable(DoubleApplicationClassNestedFunction()) == True
-    assert is_double_callable(single_application_function) == False
-    assert is_double_callable(SingleApplicationClass()) == False
+    assert is_double_callable(double_application_function) is True
+    assert is_double_callable(DoubleApplicationClass) is True
+    assert is_double_callable(DoubleApplicationClassNestedFunction()) is True
+    assert is_double_callable(single_application_function) is False
+    assert is_double_callable(SingleApplicationClass()) is False
 
 
 def test_double_to_single_signature():
@@ -80,7 +80,7 @@
     """
     assert (
         
is_double_callable(double_to_single_callable(double_application_function))
-        == False
+        is False
     )
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asgiref-3.2.10/tests/test_local.py 
new/asgiref-3.3.1/tests/test_local.py
--- old/asgiref-3.2.10/tests/test_local.py      2020-06-18 20:48:49.000000000 
+0200
+++ new/asgiref-3.3.1/tests/test_local.py       2020-11-09 16:53:04.000000000 
+0100
@@ -53,6 +53,7 @@
     # Unassigned should be an error
     with pytest.raises(AttributeError):
         test_local.foo
+
     # Assign and check it does not persist inside the thread
     class TestThread(threading.Thread):
         # Failure reason
@@ -132,6 +133,7 @@
     # Set up the local
     test_local = Local()
     test_local.foo = 3
+
     # Look at it in a sync context
     def sync_function():
         assert test_local.foo == 3
@@ -149,6 +151,7 @@
     # Set up the local
     test_local = Local()
     test_local.foo = 12
+
     # Look at it in an async context
     async def async_function():
         assert test_local.foo == 12
@@ -168,6 +171,7 @@
     # Set up the local
     test_local = Local()
     test_local.foo = 756
+
     # Look at it in an async context inside a sync context
     def sync_function():
         async def async_function():
@@ -189,6 +193,7 @@
     # Set up the local
     test_local = Local()
     test_local.foo = 8374
+
     # Make sure we go between each world at least twice
     def sync_function():
         async def async_function():
@@ -217,6 +222,7 @@
     # Set up the local
     test_local = Local(thread_critical=True)
     test_local.foo = 86
+
     # Look at it in a sync context
     def sync_function():
         with pytest.raises(AttributeError):
@@ -237,6 +243,7 @@
     # Set up the local
     test_local = Local(thread_critical=True)
     test_local.foo = 89
+
     # Look at it in an async context
     async def async_function():
         with pytest.raises(AttributeError):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asgiref-3.2.10/tests/test_server.py 
new/asgiref-3.3.1/tests/test_server.py
--- old/asgiref-3.2.10/tests/test_server.py     1970-01-01 01:00:00.000000000 
+0100
+++ new/asgiref-3.3.1/tests/test_server.py      2020-11-09 16:53:04.000000000 
+0100
@@ -0,0 +1,11 @@
+from asgiref.server import StatelessServer
+
+
+def test_stateless_server():
+    """StatlessServer can be instantiated with an ASGI 3 application."""
+
+    async def app(scope, receive, send):
+        pass
+
+    server = StatelessServer(app)
+    server.get_or_create_application_instance("scope_id", {})
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asgiref-3.2.10/tests/test_sync.py 
new/asgiref-3.3.1/tests/test_sync.py
--- old/asgiref-3.2.10/tests/test_sync.py       2020-06-18 20:48:49.000000000 
+0200
+++ new/asgiref-3.3.1/tests/test_sync.py        2020-11-09 16:53:00.000000000 
+0100
@@ -1,4 +1,5 @@
 import asyncio
+import multiprocessing
 import threading
 import time
 from concurrent.futures import ThreadPoolExecutor
@@ -279,11 +280,10 @@
         await inner()
 
     # Inner sync function
+    @sync_to_async
     def inner():
         result["thread"] = threading.current_thread()
 
-    inner = sync_to_async(inner, thread_sensitive=True)
-
     # Run it
     middle()
     assert result["thread"] == threading.current_thread()
@@ -300,22 +300,20 @@
     result_2 = {}
 
     # Outer sync function
+    @sync_to_async
     def outer(result):
         middle(result)
 
-    outer = sync_to_async(outer, thread_sensitive=True)
-
     # Middle async function
     @async_to_sync
     async def middle(result):
         await inner(result)
 
     # Inner sync function
+    @sync_to_async
     def inner(result):
         result["thread"] = threading.current_thread()
 
-    inner = sync_to_async(inner, thread_sensitive=True)
-
     # Run it (in supposed parallel!)
     await asyncio.wait([outer(result_1), inner(result_2)])
 
@@ -338,22 +336,20 @@
         await level2()
 
     # Sync level 2
+    @sync_to_async
     def level2():
         level3()
 
-    level2 = sync_to_async(level2, thread_sensitive=True)
-
     # Async level 3
     @async_to_sync
     async def level3():
         await level4()
 
     # Sync level 2
+    @sync_to_async
     def level4():
         result["thread"] = threading.current_thread()
 
-    level4 = sync_to_async(level4, thread_sensitive=True)
-
     # Run it
     level1()
     assert result["thread"] == threading.current_thread()
@@ -369,22 +365,20 @@
     result = {}
 
     # Sync level 1
+    @sync_to_async
     def level1():
         level2()
 
-    level1 = sync_to_async(level1, thread_sensitive=True)
-
     # Async level 2
     @async_to_sync
     async def level2():
         await level3()
 
     # Sync level 3
+    @sync_to_async
     def level3():
         level4()
 
-    level3 = sync_to_async(level3, thread_sensitive=True)
-
     # Async level 4
     @async_to_sync
     async def level4():
@@ -395,6 +389,29 @@
     assert result["thread"] == threading.current_thread()
 
 
+def test_thread_sensitive_disabled():
+    """
+    Tests that we can disable thread sensitivity and make things run in
+    separate threads.
+    """
+
+    result = {}
+
+    # Middle async function
+    @async_to_sync
+    async def middle():
+        await inner()
+
+    # Inner sync function
+    @sync_to_async(thread_sensitive=False)
+    def inner():
+        result["thread"] = threading.current_thread()
+
+    # Run it
+    middle()
+    assert result["thread"] != threading.current_thread()
+
+
 class ASGITest(TestCase):
     """
     Tests collection of async cases inside classes
@@ -415,3 +432,32 @@
 
     assert not asyncio.iscoroutinefunction(sync_to_async)
     assert asyncio.iscoroutinefunction(sync_to_async(sync_func))
+
+
[email protected]
+async def test_multiprocessing():
+    """
+    Tests that a forked process can use async_to_sync without it looking for
+    the event loop from the parent process.
+    """
+
+    test_queue = multiprocessing.Queue()
+
+    async def async_process():
+        test_queue.put(42)
+
+    def sync_process():
+        """Runs async_process synchronously"""
+        async_to_sync(async_process)()
+
+    def fork_first():
+        """Forks process before running sync_process"""
+        fork = multiprocessing.Process(target=sync_process)
+        fork.start()
+        fork.join(3)
+        # Force cleanup in failed test case
+        if fork.is_alive():
+            fork.terminate()
+        return test_queue.get(True, 1)
+
+    assert await sync_to_async(fork_first)() == 42
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asgiref-3.2.10/tests/test_wsgi.py 
new/asgiref-3.3.1/tests/test_wsgi.py
--- old/asgiref-3.2.10/tests/test_wsgi.py       2020-06-18 20:48:49.000000000 
+0200
+++ new/asgiref-3.3.1/tests/test_wsgi.py        2020-10-09 18:25:45.000000000 
+0200
@@ -1,3 +1,5 @@
+import sys
+
 import pytest
 
 from asgiref.testing import ApplicationCommunicator
@@ -51,6 +53,48 @@
 
 
 @pytest.mark.asyncio
+async def test_wsgi_path_encoding():
+    """
+    Makes sure the WSGI wrapper has basic functionality.
+    """
+    # Define WSGI app
+    def wsgi_application(environ, start_response):
+        assert environ["SCRIPT_NAME"] == 
"/??????".encode("utf8").decode("latin-1")
+        assert environ["PATH_INFO"] == 
"/??????".encode("utf8").decode("latin-1")
+        start_response("200 OK", [])
+        yield b""
+
+    # Wrap it
+    application = WsgiToAsgi(wsgi_application)
+    # Launch it as a test application
+    instance = ApplicationCommunicator(
+        application,
+        {
+            "type": "http",
+            "http_version": "1.0",
+            "method": "GET",
+            "path": "/??????",
+            "root_path": "/??????",
+            "query_string": b"bar=baz",
+            "headers": [],
+        },
+    )
+    await instance.send_input({"type": "http.request"})
+    # Check they send stuff
+    assert (await instance.receive_output(1)) == {
+        "type": "http.response.start",
+        "status": 200,
+        "headers": [],
+    }
+    assert (await instance.receive_output(1)) == {
+        "type": "http.response.body",
+        "body": b"",
+        "more_body": True,
+    }
+    assert (await instance.receive_output(1)) == {"type": "http.response.body"}
+
+
[email protected]
 async def test_wsgi_empty_body():
     """
     Makes sure WsgiToAsgi handles an empty body response correctly
@@ -84,6 +128,130 @@
     assert (await instance.receive_output(1)) == {"type": "http.response.body"}
 
 
[email protected]
+async def test_wsgi_clamped_body():
+    """
+    Makes sure WsgiToAsgi clamps a body response longer than Content-Length
+    """
+
+    def wsgi_application(environ, start_response):
+        start_response("200 OK", [("Content-Length", "8")])
+        return [b"0123", b"45", b"6789"]
+
+    application = WsgiToAsgi(wsgi_application)
+    instance = ApplicationCommunicator(
+        application,
+        {
+            "type": "http",
+            "http_version": "1.0",
+            "method": "GET",
+            "path": "/",
+            "query_string": b"",
+            "headers": [],
+        },
+    )
+    await instance.send_input({"type": "http.request"})
+    assert (await instance.receive_output(1)) == {
+        "type": "http.response.start",
+        "status": 200,
+        "headers": [(b"content-length", b"8")],
+    }
+    assert (await instance.receive_output(1)) == {
+        "type": "http.response.body",
+        "body": b"0123",
+        "more_body": True,
+    }
+    assert (await instance.receive_output(1)) == {
+        "type": "http.response.body",
+        "body": b"45",
+        "more_body": True,
+    }
+    assert (await instance.receive_output(1)) == {
+        "type": "http.response.body",
+        "body": b"67",
+        "more_body": True,
+    }
+    assert (await instance.receive_output(1)) == {"type": "http.response.body"}
+
+
[email protected]
+async def test_wsgi_stops_iterating_after_content_length_bytes():
+    """
+    Makes sure WsgiToAsgi does not iterate after than Content-Length bytes
+    """
+
+    def wsgi_application(environ, start_response):
+        start_response("200 OK", [("Content-Length", "4")])
+        yield b"0123"
+        pytest.fail("WsgiToAsgi should not iterate after Content-Length bytes")
+        yield b"4567"
+
+    application = WsgiToAsgi(wsgi_application)
+    instance = ApplicationCommunicator(
+        application,
+        {
+            "type": "http",
+            "http_version": "1.0",
+            "method": "GET",
+            "path": "/",
+            "query_string": b"",
+            "headers": [],
+        },
+    )
+    await instance.send_input({"type": "http.request"})
+    assert (await instance.receive_output(1)) == {
+        "type": "http.response.start",
+        "status": 200,
+        "headers": [(b"content-length", b"4")],
+    }
+    assert (await instance.receive_output(1)) == {
+        "type": "http.response.body",
+        "body": b"0123",
+        "more_body": True,
+    }
+    assert (await instance.receive_output(1)) == {"type": "http.response.body"}
+
+
[email protected]
+async def test_wsgi_multiple_start_response():
+    """
+    Makes sure WsgiToAsgi only keep Content-Length from the last call to 
start_response
+    """
+
+    def wsgi_application(environ, start_response):
+        start_response("200 OK", [("Content-Length", "5")])
+        try:
+            raise ValueError("Application Error")
+        except ValueError:
+            start_response("500 Server Error", [], sys.exc_info())
+            return [b"Some long error message"]
+
+    application = WsgiToAsgi(wsgi_application)
+    instance = ApplicationCommunicator(
+        application,
+        {
+            "type": "http",
+            "http_version": "1.0",
+            "method": "GET",
+            "path": "/",
+            "query_string": b"",
+            "headers": [],
+        },
+    )
+    await instance.send_input({"type": "http.request"})
+    assert (await instance.receive_output(1)) == {
+        "type": "http.response.start",
+        "status": 500,
+        "headers": [],
+    }
+    assert (await instance.receive_output(1)) == {
+        "type": "http.response.body",
+        "body": b"Some long error message",
+        "more_body": True,
+    }
+    assert (await instance.receive_output(1)) == {"type": "http.response.body"}
+
+
 @pytest.mark.asyncio
 async def test_wsgi_multi_body():
     """

Reply via email to