Repository: apiflask/apiflask (python)

Issue:
Specifying validation=False on @input decorator omits schema from API spec
Specifying `validation=False` in the `@input` decorator results in no input schema documentation in the generated API spec. I think the goal of #573 is to allow for input documentation to be included in the API specification without validating the input; the input validation is being skipped, but the API specification doesn't include the input documentation as expected, so it's difficult for API clients to know what data needs to be passed in.

```python
from marshmallow import fields

from apiflask import APIFlask, Schema


app = APIFlask(__name__)

class TestSchema(Schema):
    field = fields.Str()

@app.post("/test_with_validation")
@app.input(TestSchema, validation=True)
def test_with_validation():
    return "Success!"

@app.post("/test_without_validation")
@app.input(TestSchema, validation=False)
def test_without_validation():
    return "Success!"

client = app.test_client()
result = client.get("/openapi.json")
assert "requestBody" in result.json["paths"]["/test_with_validation"]["post"]  # Passes
assert "requestBody" in result.json["paths"]["/test_without_validation"]["post"]  # Fails
```

Skipping input validation should still include input schema documentation in the API spec.

Environment:

- Python version: 3.11
- Flask version: 3.1.0
- APIFlask version: 2.3.0


Pull request description:
Include input documentation in API spec when skipping validation
Modifies the behavior of specifying `validation=False` on the `@input` decorator to include input schema documentation in the generated API spec, while still skipping validation on the request input.

Fixes #626.

Checklist:

- [x] Add tests that demonstrate the correct behavior of the change. Tests should fail without the change.
- [x] Add or update relevant docs, in the `docs` folder and in code docstring.
- [x] Add an entry in `CHANGES.md` summarizing the change and linking to the issue.
- [x] Add `*Version changed*` or `*Version added*` note in any relevant docs and docstring.
- [x] Run `pytest` and `tox`, no tests failed.


Patch:
diff --git a/CHANGES.md b/CHANGES.md
index b30a68d..89f4ab4 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,12 @@
+## Version 2.3.1
+
+Released: -
+
+- Include input documentation in API spec when specifying `validation=False` on `@input` decorator  ([issue #626][issue_626]).
+
+[issue_626]: https://github.com/apiflask/apiflask/issues/626
+
+
 ## Version 2.3.0
 
 Released: 2024/11/25
diff --git a/src/apiflask/scaffold.py b/src/apiflask/scaffold.py
index ef1fbd3..767d2b1 100644
--- a/src/apiflask/scaffold.py
+++ b/src/apiflask/scaffold.py
@@ -338,16 +338,6 @@ class APIScaffold:
         def decorator(f):
             f = _ensure_sync(f)
 
-            if not validation:
-
-                @wraps(f)
-                def wrapper(*args: t.Any, **kwargs: t.Any):
-                    raw_data = _load_raw_data(location)
-                    kwargs[f'{location}_data'] = raw_data
-                    return f(*args, **kwargs)
-
-                return wrapper
-
             is_body_location = location in BODY_LOCATIONS
             if is_body_location and hasattr(f, '_spec') and 'body' in f._spec:
                 raise RuntimeError(
@@ -394,6 +384,17 @@ class APIScaffold:
                     _annotate(f, omit_default_path_parameters=True)
                 # TODO: Support set example for request parameters
                 f._spec['args'].append((schema, location))
+
+            if not validation:
+
+                @wraps(f)
+                def wrapper(*args: t.Any, **kwargs: t.Any):
+                    raw_data = _load_raw_data(location)
+                    kwargs[f'{location}_data'] = raw_data
+                    return f(*args, **kwargs)
+
+                return wrapper
+
             return use_args(
                 schema, location=location, arg_name=arg_name or f'{location}_data', **kwargs
             )(f)


Test patch:
diff --git a/tests/test_decorator_input.py b/tests/test_decorator_input.py
index 7453592..ab5b382 100644
--- a/tests/test_decorator_input.py
+++ b/tests/test_decorator_input.py
@@ -435,6 +435,16 @@ def test_skip_validation(app, client):
     assert no_validated_rv.json['json_data']['name'] == 'Kitty'
     assert no_validated_rv.json['json_data']['category'] == 'unknown'
 
+    rv = client.get('/openapi.json')
+    assert rv.status_code == 200
+    assert (
+        rv.json['paths']['/pets_without_validation/{pet_id}']['patch']['requestBody']['content'][
+            'application/json'
+        ]['schema']['$ref']
+        == '#/components/schemas/PetIn'
+    )
+    assert 'PetIn' in rv.json['components']['schemas']
+
 
 def test_location_raw_data(app):
     from apiflask.scaffold import _load_raw_data