Skip to content

Integrate/pydantic#1325

Open
sansyrox wants to merge 3 commits intomainfrom
integrate/pydantic
Open

Integrate/pydantic#1325
sansyrox wants to merge 3 commits intomainfrom
integrate/pydantic

Conversation

@sansyrox
Copy link
Member

@sansyrox sansyrox commented Mar 6, 2026

Description

This PR fixes #

Summary

This PR does....

PR Checklist

Please ensure that:

  • The PR contains a descriptive title
  • The PR contains a descriptive summary of the changes
  • You build and test your changes before submitting a PR.
  • You have added relevant documentation
  • You have added relevant tests. We prefer integration tests wherever possible

Pre-Commit Instructions:

Summary by CodeRabbit

Release Notes

  • New Features

    • Added optional Pydantic integration for automatic request body validation and type safety.
    • Automatic HTTP 422 error responses for validation failures with detailed error information.
    • Enhanced OpenAPI schema generation for Pydantic-annotated endpoints, including nested model support.
  • Tests

    • Added comprehensive test coverage for validation scenarios, error handling, and OpenAPI schema compatibility.

@vercel
Copy link

vercel bot commented Mar 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
robyn Ready Ready Preview, Comment Mar 6, 2026 0:30am

@coderabbitai
Copy link

coderabbitai bot commented Mar 6, 2026

📝 Walkthrough

Walkthrough

Adds optional Pydantic model support to the framework with conditional imports, validation utilities, and OpenAPI schema integration. Includes lazy-loaded helpers for type checking and validation, integration points in request routing and schema generation, and comprehensive test coverage with new test routes and validation test suites.

Changes

Cohort / File(s) Summary
Pydantic Support Core
robyn/pydantic_support.py, robyn/router.py, robyn/openapi.py
New module with Pydantic helpers (lazy loading, validation, schema extraction); integrated into router for request body validation with 422 error handling; extended OpenAPI generation to recognize and schema-ify Pydantic models via component references.
Integration Test Routes & Tests
integration_tests/base_routes.py, integration_tests/test_pydantic.py, integration_tests/test_openapi.py
New Pydantic-based routes (sync/async variants with nested models and Request objects); comprehensive test suite covering valid bodies, defaults, missing fields, type validation, extra fields, nested models, PUT method, and empty bodies; OpenAPI schema validation tests for request bodies and nested model references.
Dependencies & Exports
pyproject.toml, robyn/types.py
Added pydantic >=2.0.0 as optional dependency in both setuptools and Poetry configurations with extras mapping; expanded all in types.py to export additional type aliases (Directory, PathParams, Method, FormData, Files, IPAddress).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Router
    participant PydanticValidator
    participant Handler
    participant Response

    Client->>Router: POST /endpoint with JSON body
    Router->>Router: Detect Pydantic params in handler signature
    Router->>PydanticValidator: validate_pydantic_body(model_class, body)
    alt Validation Success
        PydanticValidator-->>Router: (model_instance, None)
        Router->>Handler: Call with validated Pydantic instance
        Handler-->>Response: Process and return data
        Response-->>Client: 200 OK
    else Validation Failure
        PydanticValidator-->>Router: (None, error_dict)
        Router-->>Client: 422 Unprocessable Entity with error details
    end
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A fuzzy feature hops into place,
Pydantic models validate with grace,
Request bodies checked, schemas are refined,
Type safety and OpenAPI aligned—
Integration complete, the tests are in sight!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is incomplete; it contains only template placeholders ('This PR fixes #', 'This PR does....') with no actual details about the Pydantic integration changes. Fill in the description with details about what Pydantic integration adds, which issue is fixed, what was tested, and confirm that the checklist items (descriptive title, tests, documentation) have been completed.
Title check ❓ Inconclusive The title 'Integrate/pydantic' is vague and uses a forward slash convention that lacks clarity; it doesn't clearly convey that Pydantic models are being integrated into the core framework. Revise the title to be more descriptive, such as 'Add Pydantic model support for request validation and OpenAPI schema generation' to better communicate the main change.
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch integrate/pydantic

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codspeed-hq
Copy link

codspeed-hq bot commented Mar 6, 2026

Merging this PR will not alter performance

✅ 177 untouched benchmarks


Comparing integrate/pydantic (7662bd5) with main (7aeaef4)

Open in CodSpeed

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (5)
robyn/pydantic_support.py (2)

140-146: Consider including the full error detail in the exception message.

The PydanticBodyValidationError exception only includes the "error" key in its message. For debugging in logs, it might be helpful to include a summary of the detail.

♻️ Optional: Include detail count in message
     def __init__(self, error_detail: dict):
         self.error_detail = error_detail
-        super().__init__(error_detail.get("error", "Validation Error"))
+        detail = error_detail.get("detail", [])
+        detail_count = len(detail) if isinstance(detail, list) else 1
+        super().__init__(f"{error_detail.get('error', 'Validation Error')} ({detail_count} error(s))")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@robyn/pydantic_support.py` around lines 140 - 146, Update
PydanticBodyValidationError.__init__ to include the full error_detail (or a
concise summary such as the number of errors plus a short snippet) in the
exception message rather than only error_detail.get("error"); use the incoming
error_detail dict to build the message (e.g., f"Validation Error:
{error_detail}" or f"Validation Error ({len(error_detail.get('errors', []))}):
{some_summary}") and pass that string to super().__init__ so logs contain the
complete/summary information for easier debugging while still storing
error_detail on the instance.

108-117: Edge case: _ValidationError could be None in except clause.

If _ensure_pydantic() runs but Pydantic isn't installed, _ValidationError remains None. The except _ValidationError as e: clause would then raise TypeError: catching classes that do not inherit from BaseException is not allowed.

While this is prevented at route registration time by check_pydantic_installed_for_handler, a defensive check would make the code more robust:

🛡️ Suggested defensive check
 def validate_pydantic_body(model_class, body: Any) -> Tuple[Any, Optional[dict]]:
     ...
     _ensure_pydantic()
+    if _ValidationError is None:
+        return None, {"detail": "Pydantic not installed", "error": "Configuration Error"}
     try:
         ...
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@robyn/pydantic_support.py` around lines 108 - 117, The except block assumes
_ValidationError is a valid exception class; make it defensive by replacing the
two separate except clauses with a single "except Exception as e:" and inside it
branch: if _ValidationError is not None and isinstance(e, _ValidationError)
return the sanitized validation response (use _sanitize_errors(e.errors())),
otherwise return the generic invalid request body response (str(e)). Update the
error-handling in the function that calls _ensure_pydantic() (the block
containing the current "except _ValidationError as e:" and "except Exception as
e:") to use this conditional isinstance check against _ValidationError to avoid
catching a None value.
integration_tests/test_pydantic.py (2)

5-10: Minor: Unused import and inline imports pattern.

The pydantic import on line 6 is only used for checking availability. Consider using import pydantic as _pydantic to signal it's intentionally unused, or simplify to just the import statement without assignment.

Also, the import requests statements inside test_pydantic_invalid_json, test_pydantic_put, and test_pydantic_empty_body could be hoisted to the module level for consistency with other test files.

♻️ Suggested refactor
 try:
-    import pydantic
+    import pydantic as _pydantic  # noqa: F401
 
     _HAS_PYDANTIC = True
 except ImportError:
     _HAS_PYDANTIC = False

And add import requests at the top of the file alongside from integration_tests.helpers.http_methods_helpers import json_post.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@integration_tests/test_pydantic.py` around lines 5 - 10, Change the top-level
pydantic import to make its unused status explicit (e.g., import pydantic as
_pydantic) and keep the feature-detection boolean _HAS_PYDANTIC as-is; then
hoist the inline imports of requests from inside test_pydantic_invalid_json,
test_pydantic_put, and test_pydantic_empty_body to the module level (add import
requests alongside the existing from
integration_tests.helpers.http_methods_helpers import json_post) so all imports
are consistent and no per-test inline imports remain.

80-82: Nitpick: Redundant .lstrip("/").

The endpoint variable from the f-string already starts with /, so .lstrip("/") works correctly, but the pattern is slightly verbose. Consider using endpoint[1:] or defining the endpoint without the leading slash initially.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@integration_tests/test_pydantic.py` around lines 80 - 82, The endpoint string
construction uses an unnecessary .lstrip("/") on endpoint; update the code that
sets endpoint (the variable named endpoint built from
f"/{function_type}/pydantic/user") to avoid the redundant call—either build
endpoint without the leading slash (e.g., f"{function_type}/pydantic/user") or
replace .lstrip("/") with a simple slice endpoint[1:] where the caller expects
no leading slash; adjust the requests.post call to use the resulting endpoint
variable as before.
robyn/openapi.py (1)

318-337: Potential schema name collision when merging component schemas.

The component_schemas.update() call on line 321 will overwrite existing schemas with the same name. If two different Pydantic models have nested models with identical names but different definitions, the later one will silently overwrite the earlier one.

Consider adding a warning or check for schema conflicts:

🛡️ Optional: Warn on schema name collision
 if is_pydantic_model(request_body):
     schema, component_schemas = get_pydantic_openapi_schema(request_body)
     if component_schemas:
+        for name in component_schemas:
+            if name in self.openapi_spec["components"]["schemas"]:
+                existing = self.openapi_spec["components"]["schemas"][name]
+                if existing != component_schemas[name]:
+                    import logging
+                    logging.getLogger(__name__).warning(
+                        f"OpenAPI schema '{name}' already exists with different definition"
+                    )
         self.openapi_spec["components"]["schemas"].update(component_schemas)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@robyn/openapi.py` around lines 318 - 337, The current merge in
get_pydantic_openapi_schema handling overwrites existing component schemas via
self.openapi_spec["components"]["schemas"].update(component_schemas), which can
silently replace a different schema with the same name; modify the merge so you
iterate each key in component_schemas, check if that key already exists in
self.openapi_spec["components"]["schemas"], compare the existing definition with
the new one, and on difference either log/warn (e.g., using self.logger.warning
or warnings.warn) or disambiguate (e.g., rename the new schema key or raise an
error) before inserting—change this logic inside the branch where
is_pydantic_model(request_body) is handled (around get_pydantic_openapi_schema
usage) to prevent silent collisions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@integration_tests/test_pydantic.py`:
- Around line 5-10: Change the top-level pydantic import to make its unused
status explicit (e.g., import pydantic as _pydantic) and keep the
feature-detection boolean _HAS_PYDANTIC as-is; then hoist the inline imports of
requests from inside test_pydantic_invalid_json, test_pydantic_put, and
test_pydantic_empty_body to the module level (add import requests alongside the
existing from integration_tests.helpers.http_methods_helpers import json_post)
so all imports are consistent and no per-test inline imports remain.
- Around line 80-82: The endpoint string construction uses an unnecessary
.lstrip("/") on endpoint; update the code that sets endpoint (the variable named
endpoint built from f"/{function_type}/pydantic/user") to avoid the redundant
call—either build endpoint without the leading slash (e.g.,
f"{function_type}/pydantic/user") or replace .lstrip("/") with a simple slice
endpoint[1:] where the caller expects no leading slash; adjust the requests.post
call to use the resulting endpoint variable as before.

In `@robyn/openapi.py`:
- Around line 318-337: The current merge in get_pydantic_openapi_schema handling
overwrites existing component schemas via
self.openapi_spec["components"]["schemas"].update(component_schemas), which can
silently replace a different schema with the same name; modify the merge so you
iterate each key in component_schemas, check if that key already exists in
self.openapi_spec["components"]["schemas"], compare the existing definition with
the new one, and on difference either log/warn (e.g., using self.logger.warning
or warnings.warn) or disambiguate (e.g., rename the new schema key or raise an
error) before inserting—change this logic inside the branch where
is_pydantic_model(request_body) is handled (around get_pydantic_openapi_schema
usage) to prevent silent collisions.

In `@robyn/pydantic_support.py`:
- Around line 140-146: Update PydanticBodyValidationError.__init__ to include
the full error_detail (or a concise summary such as the number of errors plus a
short snippet) in the exception message rather than only
error_detail.get("error"); use the incoming error_detail dict to build the
message (e.g., f"Validation Error: {error_detail}" or f"Validation Error
({len(error_detail.get('errors', []))}): {some_summary}") and pass that string
to super().__init__ so logs contain the complete/summary information for easier
debugging while still storing error_detail on the instance.
- Around line 108-117: The except block assumes _ValidationError is a valid
exception class; make it defensive by replacing the two separate except clauses
with a single "except Exception as e:" and inside it branch: if _ValidationError
is not None and isinstance(e, _ValidationError) return the sanitized validation
response (use _sanitize_errors(e.errors())), otherwise return the generic
invalid request body response (str(e)). Update the error-handling in the
function that calls _ensure_pydantic() (the block containing the current "except
_ValidationError as e:" and "except Exception as e:") to use this conditional
isinstance check against _ValidationError to avoid catching a None value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b662b9ab-0ab8-4093-a42d-58f2c9bfe2e9

📥 Commits

Reviewing files that changed from the base of the PR and between 7aeaef4 and 7662bd5.

📒 Files selected for processing (8)
  • integration_tests/base_routes.py
  • integration_tests/test_openapi.py
  • integration_tests/test_pydantic.py
  • pyproject.toml
  • robyn/openapi.py
  • robyn/pydantic_support.py
  • robyn/router.py
  • robyn/types.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant