Repository: adamchainz/apig-wsgi

Issue:
Error when requestContext is None
### Python Version

3.8

### Package Version

2.12.0

### Description

I have a lambda that is called through API Gateway, but can also be invoked directly via a lambda client.  Upgrading to 2.12.0 started causing this error when invoked via client.
```
[ERROR] TypeError: argument of type 'NoneType' is not iterable
Traceback (most recent call last):
  File "/var/task/apig_wsgi/__init__.py", line 61, in handler
    if "requestContext" in event and "elb" in event["requestContext"]:
```

This is being done from Kotlin code, using `com.amazonaws.services.lambda.AWSLambda` client.  When an `AwsProxyRequest` object is constructed, it has `requestContext=null` by default.  This results in the error above.

I can update my invoke code to set a dummy requestContext, but I do believe it is valid to be None in the direct lambda invocation use case.  Does it make sense to add a `not None` check here for requestContext?

Thanks.



Discussion:

Patch:
diff --git a/HISTORY.rst b/HISTORY.rst
index b2d240b..c1e65a8 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -2,6 +2,10 @@
 History
 =======
 
+* Handle ``requestContext`` being ``None``, as can happen from Lambda invoke.
+
+  Thanks to scottmn for the report in `Issue #289 <https://github.com/adamchainz/apig-wsgi/issues/289>`__.
+
 2.12.0 (2021-08-24)
 -------------------
 
diff --git a/src/apig_wsgi/__init__.py b/src/apig_wsgi/__init__.py
index 2d1389d..0217ad6 100644
--- a/src/apig_wsgi/__init__.py
+++ b/src/apig_wsgi/__init__.py
@@ -58,7 +58,11 @@ def make_lambda_handler(
 
     def handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
         # ALB doesn't send a version, but requestContext will contain a key named 'elb'.
-        if "requestContext" in event and "elb" in event["requestContext"]:
+        if (
+            "requestContext" in event
+            and isinstance(event["requestContext"], dict)
+            and "elb" in event["requestContext"]
+        ):
             version = "alb"
         else:
             version = event.get("version", "1.0")


Test patch:
diff --git a/tests/test_apig_wsgi.py b/tests/test_apig_wsgi.py
index 7697d40..528ae91 100644
--- a/tests/test_apig_wsgi.py
+++ b/tests/test_apig_wsgi.py
@@ -1,7 +1,17 @@
 import sys
 from base64 import b64encode
 from io import BytesIO
-from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Tuple
+from typing import (
+    Any,
+    Callable,
+    Dict,
+    Generator,
+    Iterable,
+    List,
+    Optional,
+    Tuple,
+    Union,
+)
 
 import pytest
 
@@ -82,6 +92,13 @@ class ContextStub:
 # v1 tests
 
 
+class Sentinel:
+    pass
+
+
+SENTINEL = Sentinel()
+
+
 def make_v1_event(
     *,
     method: str = "GET",
@@ -92,7 +109,7 @@ def make_v1_event(
     headers_multi: bool = True,
     body: str = "",
     binary: bool = False,
-    request_context: Optional[Dict[str, Any]] = None,
+    request_context: Union[Dict[str, Any], None, Sentinel] = SENTINEL,
 ) -> Dict[str, Any]:
     if headers is None:
         headers = {"Host": ["example.com"]}
@@ -125,7 +142,7 @@ def make_v1_event(
         event["body"] = body
         event["isBase64Encoded"] = False
 
-    if request_context is not None:
+    if request_context is not SENTINEL:
         event["requestContext"] = request_context
 
     return event
@@ -507,6 +524,14 @@ class TestV1Events:
 
         assert simple_app.environ["apig_wsgi.request_context"] == context
 
+    def test_request_context_none(self, simple_app: App) -> None:
+        # Invoking lambdas can lead to requestContext being JSON null
+        event = make_v1_event(request_context=None)
+
+        simple_app.handler(event, None)
+
+        # Simply don't crash
+
     def test_full_event(self, simple_app: App) -> None:
         event = make_v1_event()