Repository: marshmallow-code/apispec
Pull request #436 (2019-04-23 15:09:29)

Fix stringification of response codes
OpenAPI 2 states that the available status codes are described by RFC
7231. The RFC states that the, "status-code element is a three-digit integer
code", therefore we interpret that as OpenAPI 2 does not allow
non-integer characters to be present in status codes.

OpenAPI 3 on the other hand is a lot less ambiguous and states that the
status code field "MAY contain the uppercase wild character X",
therefore meaning non-integer characters are valid (e.g. '2XX').

Also add support for parsing Python 3's in-built http.HTTPStatus.

Addresses issue #426 

lafrech: Thanks for the PR. Comments inline.
DStape: Latest push made 3 changes:

1) now using ```@pytest.mark.skipif```
2) removed test code duplication
3) bool comparisons using ```is``` rather than ```==```
DStape: Ran through code formatter (```black```). Now expecting CI job to pass...

Patch:
diff --git a/src/apispec/core.py b/src/apispec/core.py
index c85fb30..b26699a 100644
--- a/src/apispec/core.py
+++ b/src/apispec/core.py
@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 """Core apispec classes and functions."""
 from collections import OrderedDict
+import warnings
 
 from apispec.compat import iterkeys, iteritems
 from .exceptions import (
@@ -69,6 +70,12 @@ def clean_operations(operations, openapi_major_version):
         if "responses" in operation:
             responses = OrderedDict()
             for code, response in iteritems(operation["responses"]):
+                try:
+                    code = int(code)  # handles IntEnums like http.HTTPStatus
+                except (TypeError, ValueError):
+                    if openapi_major_version < 3:
+                        warnings.warn("Non-integer code not allowed in OpenAPI < 3")
+
                 responses[str(code)] = get_ref(
                     "response", response, openapi_major_version
                 )


Test patch:
diff --git a/tests/test_core.py b/tests/test_core.py
index bff4193..986012e 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -2,6 +2,7 @@
 from collections import OrderedDict
 
 import pytest
+import sys
 import yaml
 
 from apispec import APISpec, BasePlugin
@@ -422,6 +423,34 @@ class TestPath:
                 == metadata["components"]["responses"]["test_response"]
             )
 
+    @pytest.mark.skipif(
+        int(sys.version[0]) == 2, reason="HTTPStatus only available in Python3"
+    )
+    def test_response_with_HTTPStatus_code(self, spec):
+        from http import HTTPStatus
+
+        code = HTTPStatus(200)
+        spec.path(
+            path="/pet/{petId}",
+            operations={"get": {"responses": {code: "test_response"}}},
+        )
+
+        assert "200" in get_paths(spec)["/pet/{petId}"]["get"]["responses"]
+
+    def test_response_with_status_code_range(self, spec, recwarn):
+        status_code = "2XX"
+
+        spec.path(
+            path="/pet/{petId}",
+            operations={"get": {"responses": {status_code: "test_response"}}},
+        )
+
+        if spec.openapi_version.major < 3:
+            assert len(recwarn) == 1
+            assert recwarn.pop(UserWarning)
+
+        assert status_code in get_paths(spec)["/pet/{petId}"]["get"]["responses"]
+
     def test_path_check_invalid_http_method(self, spec):
         spec.path("/pet/{petId}", operations={"get": {}})
         spec.path("/pet/{petId}", operations={"x-dummy": {}})
diff --git a/tests/test_ext_marshmallow_openapi.py b/tests/test_ext_marshmallow_openapi.py
index cce5006..8b39d82 100644
--- a/tests/test_ext_marshmallow_openapi.py
+++ b/tests/test_ext_marshmallow_openapi.py
@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
 
 import re
-import warnings
 
 import pytest
 from pytest import mark
@@ -380,7 +379,7 @@ class TestMarshmallowSchemaToModelDefinition:
         res = openapi.schema2jsonschema(WhiteStripesSchema)
         assert set(res["properties"].keys()) == {"guitarist", "drummer"}
 
-    def test_only_explicitly_declared_fields_are_translated(self, recwarn, openapi):
+    def test_only_explicitly_declared_fields_are_translated(self, openapi):
         class UserSchema(Schema):
             _id = fields.Int()
 
@@ -388,19 +387,15 @@ class TestMarshmallowSchemaToModelDefinition:
                 title = "User"
                 fields = ("_id", "email")
 
-        with warnings.catch_warnings():
-            warnings.simplefilter("always")
+        with pytest.warns(
+            UserWarning,
+            match="Only explicitly-declared fields will be included in the Schema Object.",
+        ):
             res = openapi.schema2jsonschema(UserSchema)
             assert res["type"] == "object"
             props = res["properties"]
             assert "_id" in props
             assert "email" not in props
-            warning = recwarn.pop()
-            expected_msg = (
-                "Only explicitly-declared fields will be included in the Schema Object."
-            )
-            assert expected_msg in str(warning.message)
-            assert issubclass(warning.category, UserWarning)
 
     def test_observed_field_name_for_required_field(self, openapi):
         if MARSHMALLOW_VERSION_INFO[0] < 3: