Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sandbox: Enable mypy type checker #560

Merged
merged 1 commit into from
Oct 30, 2024
Merged

Sandbox: Enable mypy type checker #560

merged 1 commit into from
Oct 30, 2024

Conversation

amotl
Copy link
Collaborator

@amotl amotl commented Oct 27, 2024

About

This patch enables mypy to be invoked on poe check, thus also slotting it into CI checks on GHA.

Status

It will need more work after other feature patches have been merged. Hereby handing this in as a question Further information is requested asking for feedback if you appreciate or discourage this improvement to the code base.

Copy link

coderabbitai bot commented Oct 27, 2024

Caution

Review failed

The pull request is closed.

Walkthrough

This pull request introduces several modifications across multiple files, primarily focusing on enhancing type annotations, error handling, and method signatures in the responder module. Key changes include the addition of type ignore comments for status_code assignments, updates to method signatures to conform to Python standards, and improvements in error handling within classes like Request, Response, and Router. The setup.py file is also updated to include mypy in the test dependencies. Overall, these changes aim to improve code clarity and robustness.

Changes

File Change Summary
responder/api.py - Updated redirect method signature to include self and added type ignore for status_code.
- Added type ignore for resp.status_code in _static_response.
- Removed debug parameter from serve method's uvicorn.run() call.
responder/ext/schema/init.py - Added type ignore for resp.status_code in schema_response method.
responder/models.py - Enhanced type annotations for CaseInsensitiveDict, SimpleCookie, and Response attributes.
- Refactored media method for better error handling.
responder/routes.py - Added type annotation for apps attribute in Router.
- Refined error handling for missing endpoint methods with type ignores.
- Updated mount method to use a dictionary for apps updates.
setup.py - Modified extras_require to include mypy in test dependencies and reordered the list.

Possibly related PRs

Suggested labels

needs BDFL approval, documentation

Suggested reviewers

  • kennethreitz
  • tabotkevin

Poem

🐇 In the code where bunnies hop,
Type hints and ignores never stop.
With each method, clearer we see,
A robust API, as fine as can be!
So let’s celebrate with joy and cheer,
For better code is finally here! 🌟


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@amotl amotl added python Pull requests that update Python code qa and testing Quality assurance and software testing question Further information is requested labels Oct 27, 2024
@amotl amotl marked this pull request as ready for review October 27, 2024 21:58
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.

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (2)
responder/routes.py (1)

Line range hint 1-359: Consider enhancing type safety across the router implementation.

While the current changes enable mypy and handle known type checking limitations, there are opportunities to enhance type safety:

  1. Add type annotations to method parameters and return types
  2. Consider using TypeVar for generic type parameters
  3. Use Protocol classes to define interface contracts for ASGI/WSGI apps
  4. Add type annotations to class variables and instance attributes

This would provide better type safety and improved IDE support.

responder/api.py (1)

228-233: LGTM: Type ignore is consistent with usage pattern.

The type ignore for the status_code parameter is consistent with the previous usage pattern. Consider updating the docstring to mention that status_code accepts HTTP status codes from the status_codes module.

Update the docstring parameter description:

-        :param status_code: an `API.status_codes` attribute, or an integer,
-                            representing the HTTP status code of the redirect.
+        :param status_code: An HTTP status code from `API.status_codes` module (e.g., 
+                           status_codes.HTTP_301) or an integer, representing the 
+                           HTTP status code of the redirect.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 98ca450 and d266322.

⛔ Files ignored due to path filters (1)
  • pyproject.toml is excluded by !**/*.toml
📒 Files selected for processing (5)
  • responder/api.py (2 hunks)
  • responder/ext/schema/init.py (1 hunks)
  • responder/models.py (7 hunks)
  • responder/routes.py (6 hunks)
  • setup.py (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • responder/ext/schema/init.py
🔇 Additional comments (11)
responder/routes.py (4)

125-125: Type ignore comment is appropriate.

The # type: ignore[attr-defined] comment is correctly used here as mypy cannot verify the existence of the status code attribute at runtime. This is a common pattern when working with Starlette's HTTPException.


139-139: Type ignore comment is appropriate.

The # type: ignore[attr-defined] comment is correctly used here for the status code assignment, maintaining consistency with similar patterns in the codebase.


259-259: LGTM! The dictionary update is more explicit and type-safe.

The change to use update({route: app}) instead of update(route, app) makes the key-value relationship clearer and aligns well with the type annotation.


293-298: ⚠️ Potential issue

Address the FIXME comment while verifying the error handling.

While the # type: ignore[attr-defined] comment is appropriate, there's an unaddressed FIXME comment that needs attention. The error handling here seems minimal, and the response object is created but not used (marked with noqa: F841).

Consider enhancing the error handling:

async def default_response(self, scope, receive, send):
    if scope["type"] == "websocket":
        websocket_close = WebSocketClose()
        await websocket_close(scope, receive, send)
        return

    request = Request(scope, receive)
    response = Response(request, formats=get_formats())
    response.status_code = status_codes.HTTP_404  # type: ignore[attr-defined]
    await response(scope, receive, send)

Let's verify if this pattern is consistent across the codebase:

responder/models.py (5)

110-110: LGTM: Proper type annotation for headers dictionary.

The type annotation CaseInsensitiveDict correctly specifies the type of the headers dictionary, improving type safety.


153-153: LGTM: Proper type annotation for cookie parsing.

The type annotation SimpleCookie correctly specifies the type of the cookie parser, improving type safety.


242-255: LGTM: Improved error handling and type safety in media processing.

The changes properly handle different format types with appropriate error messages:

  • Validates string formats against available formatters
  • Verifies callable formats
  • Raises clear error messages for invalid inputs

289-290: LGTM: Proper type annotations for Response class attributes.

The type annotations are correct and improve type safety:

  • status_code: t.Union[int, None] properly handles the nullable status code
  • cookies: SimpleCookie correctly specifies the cookie container type

Also applies to: 300-300


380-382: LGTM: Improved type safety and error handling in response generation.

The changes enhance reliability:

  • Proper type annotation for response class selection
  • Runtime validation prevents undefined status codes from propagating

Also applies to: 388-390

responder/api.py (2)

373-373: Verify the removal of debug parameter from uvicorn.run.

The debug parameter has been removed from uvicorn.run() but is still accepted in the method signature and used in the run() method. This could lead to confusion as the parameter would have no effect when using serve() directly.

Let's verify the impact:

#!/bin/bash
# Check if debug parameter is used consistently across the codebase
echo "Searching for debug parameter usage in serve/run methods:"
rg "def (serve|run).*debug.*:" -A 5

echo "Checking uvicorn.run usage patterns:"
rg "uvicorn\.run.*debug.*" -A 2

Please clarify:

  1. Should the debug parameter be removed from the serve method signature?
  2. Or should it be passed to uvicorn.run?

224-224: LGTM: Type ignore is appropriate here.

The type ignore for status_code is necessary as mypy cannot determine that it's a valid attribute. This is a known limitation with Starlette's type definitions.

Let's verify if this is a common pattern across the codebase:

✅ Verification successful

The previous search might have failed due to the specific pattern. Let's try these broader searches to understand the usage pattern of status codes and verify if the type ignore is appropriate.


Type ignore for status_code is consistent with codebase patterns

Based on the search results, the type ignore for status_code is appropriate and consistent with the codebase's patterns. We can see similar type ignores for status_code assignments across multiple files:

  • responder/routes.py: response.status_code = status_codes.HTTP_200 # type: ignore[attr-defined]
  • responder/ext/schema/__init__.py: resp.status_code = status_codes.HTTP_200 # type: ignore[attr-defined]

This is a known limitation with Starlette's type definitions, and the codebase consistently handles it with type ignores.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for similar status_code assignments to understand the pattern
rg "status_code.*=.*status_codes\.HTTP_" -A 2

Length of output: 641


Script:

#!/bin/bash
# Let's try a more comprehensive search for status_code assignments
rg "\.status_code\s*=" -A 2

# Also search for HTTP status code imports and usage
rg "from.*status_codes.*import|status_codes\.HTTP" -A 2

# Let's also check the specific file
cat responder/api.py

Length of output: 20666

setup.py Show resolved Hide resolved
@@ -207,7 +208,7 @@ class Router:
def __init__(self, routes=None, default_response=None, before_requests=None):
self.routes = [] if routes is None else list(routes)
# [TODO] Make its own router
self.apps = {}
self.apps: t.Dict[str, t.Any] = {}
Copy link

@coderabbitai coderabbitai bot Oct 27, 2024

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider using a more specific type annotation for apps.

While t.Dict[str, t.Any] is technically correct, it's quite permissive. Based on the usage in the mount method and the docstring "Mounts ASGI / WSGI applications", consider using a more specific type like:

from typing import Union, Callable, Protocol

class ASGIApp(Protocol):
    async def __call__(self, scope: dict, receive: Callable, send: Callable) -> None: ...

apps: t.Dict[str, Union[ASGIApp, 'WSGIApplication']]

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That's a good suggestion. I will try, thanks.

Copy link

Choose a reason for hiding this comment

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

@amotl, glad you found my suggestion helpful! Let me know if you have any questions or need further assistance.

 /\_/\
( o.o )
 > ^ <

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I tried, but mypy will complain then:

responder/routes.py:352: error: Incompatible types in "await" (actual type "Awaitable[None] | Iterable[bytes]", expected type "Awaitable[Any]")  [misc]
responder/routes.py:352: error: Too many arguments  [call-arg]
Found 2 errors in 1 file (checked 18 source files)
Error: Sequence aborted after failed subtask 'lint[3]'

Copy link

Choose a reason for hiding this comment

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

@amotl, it appears that the type annotations I suggested for apps are causing mypy to raise errors because app can be either an ASGI application (awaitable) or a WSGI application (non-awaitable). This leads to type inconsistencies when attempting to await app(scope, receive, send).

To address this, we can adjust the type annotations to accurately reflect the possible types of app and modify the code to handle both cases explicitly. Here's a revised approach:

  1. Update the type annotations:

    from typing import Union, Callable, Awaitable, Any
    
    ASGIApp = Callable[[dict, Callable, Callable], Awaitable[None]]
    WSGIApp = Callable[..., Any]  # WSGI applications accept 'environ' and 'start_response'
    
    apps: t.Dict[str, Union[ASGIApp, WSGIApp]]
  2. Modify the __call__ method to handle both ASGI and WSGI apps:

    async def __call__(self, scope, receive, send):
        # ... existing code ...
    
        # Call into a submounted app, if one exists.
        for path_prefix, app in self.apps.items():
            if path.startswith(path_prefix):
                scope["path"] = path[len(path_prefix) :]
                scope["root_path"] = root_path + path_prefix
                if asyncio.iscoroutinefunction(app):
                    # ASGI application
                    await app(scope, receive, send)
                else:
                    # WSGI application
                    app = WSGIMiddleware(app)
                    await app(scope, receive, send)
                return
    
        # ... existing code ...

This way, we explicitly check if app is an awaitable ASGI application or a regular WSGI application and handle each case accordingly. This should resolve the mypy errors by ensuring type consistency.

Let me know if this helps or if you have further questions.

(\_ _/)
( •_• )
/ >💭< \

Copy link
Collaborator Author

@amotl amotl Oct 30, 2024

Choose a reason for hiding this comment

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

This definition works well:

from starlette.types import ASGIApp

self.apps: t.Dict[str, ASGIApp] = {}

Copy link

Choose a reason for hiding this comment

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

@amotl, I'm glad to hear that using self.apps: t.Dict[str, ASGIApp] = {} works well for you! If your application only needs to handle ASGI applications, this type annotation is appropriate and keeps the code straightforward. Thanks for sharing your solution.


✏️ Learnings added
Learnt from: amotl
PR: kennethreitz/responder#560
File: responder/routes.py:0-0
Timestamp: 2024-10-30T21:57:29.151Z
Learning: In `responder/routes.py`, when only ASGI applications are being mounted, it's acceptable to annotate `self.apps` as `t.Dict[str, ASGIApp]` in the `Router` class.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Collaborator Author

@amotl amotl Oct 30, 2024

Choose a reason for hiding this comment

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

Thanks for your suggestion. I've deferred it to a dedicated issue, so it will not be forgotten.

Copy link

Choose a reason for hiding this comment

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

@amotl, understood. Thank you for creating a dedicated issue (GH-566). Let me know if you need any further assistance.

responder/models.py Show resolved Hide resolved
Copy link

@llamapreview llamapreview bot left a comment

Choose a reason for hiding this comment

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

Auto Pull Request Review from LlamaPReview

1. Overview

1.1 PR Summary

  • Purpose: Integrate mypy type checking into the responder project to improve code quality and type safety.
  • Key Components Modified: pyproject.toml, responder/api.py, responder/ext/schema/__init__.py, responder/models.py, responder/routes.py, and setup.py.
  • Impact Assessment: Enhances code maintainability, reduces runtime errors, and improves overall code quality.

1.2 Architecture Changes

  • System Design Modifications: Introduction of static type checking using mypy.
  • Component Interactions: Updates to the CI/CD pipeline to include mypy checks.
  • Integration Points: Changes primarily affect the responder module and the CI/CD pipeline.

2. Detailed Technical Analysis

2.1 Code Logic Deep-Dive

Core Logic Changes

responder/models.py - Response class

  • Submitted PR Code:
    if self.status_code is None:
        raise RuntimeError("HTTP status code has not been defined")
  • Analysis:
    • Logic Flow Evaluation: The status code validation logic is duplicated in the __call__ and ok methods.
    • Edge Cases Consideration: Handling of status_code in the Response class.
    • Potential Issues/Bugs: Duplicated logic can lead to inconsistencies and maintenance issues.
  • LlamaPReview Suggested Improvements:
    class Response:
        def _validate_status_code(self) -> None:
            if self.status_code is None:
                raise RuntimeError("HTTP status code has not been defined")
    
        async def __call__(self, scope, receive, send):
            body, headers = await self.body
            if self.headers:
                headers.update(self.headers)
            self._validate_status_code()
            response = response_cls(body, status_code=self.status_code, headers=headers)
            self._prepare_cookies(response)
            await response(scope, receive, send)
    
        @property
        def ok(self):
            self._validate_status_code()
            return 200 <= self.status_code < 300

Algorithm & Data Structure Analysis

  • Complexity Analysis: The changes introduce minimal additional complexity due to type annotations and validation logic.
  • Performance Implications: Minimal impact on performance. The main impact will be on the build times due to type checking.
  • Memory Usage Considerations: No significant memory usage concerns identified.

2.2 Implementation Quality

  • Code Organization and Structure: The code is generally well-organized. However, there are inconsistencies in type annotations.
  • Design Patterns Usage: Follows best practices for type annotations as outlined in PEP 484.
  • Error Handling Approach: Improved with the introduction of type checking.
  • Resource Management: No potential memory leaks or resource management issues identified.

3. Critical Findings

3.1 Potential Issues

  • 🔴 Critical Issues

    • Issue Description: Inconsistent type annotations across different files.
    • Impact: Can lead to confusion and potential type errors.
    • Recommendation: Standardize the use of type annotations across the project.
  • 🟡 Warnings

    • Warning Description: The CI/CD pipeline may fail due to new type checking rules.
    • Potential Risks: Type errors during the build process.
    • Suggested Improvements: Ensure that all type annotations are correct and that the CI pipeline is configured to handle type errors gracefully.

3.2 Code Quality Concerns

  • Maintainability Aspects: The introduction of type checking will improve maintainability by catching type-related errors early.
  • Readability Issues: Inconsistent type annotations can reduce readability.
  • Performance Bottlenecks: Minimal impact on performance. The main impact will be on the build times due to type checking.

4. Security Assessment

4.1 Security Considerations

  • Authentication/Authorization Impacts: None identified.
  • Data Handling Concerns: None identified.
  • Input Validation: No changes to input validation mechanisms.
  • Security Best Practices: Follow best practices for type annotations and error handling.

4.2 Vulnerability Analysis

  • Potential Security Risks: None identified.
  • Mitigation Strategies: Ensure proper handling of type errors in the CI/CD pipeline.
  • Security Testing Requirements: Test the integration of mypy with the CI/CD pipeline.

5. Testing Strategy

5.1 Test Coverage

  • Unit Test Analysis: Ensure all type-annotated functions are tested.
  • Integration Test Requirements: Test the integration of mypy with the CI/CD pipeline.
  • Edge Cases Coverage: Test the handling of status_code in the Response class.

5.2 Test Recommendations

Suggested Test Cases

def test_response_status_code_validation():
    response = Response(req=None, formats=None)
    with pytest.raises(RuntimeError):
        response.ok
    with pytest.raises(RuntimeError):
        await response(scope=None, receive=None, send=None)
  • Coverage Improvements: Aim for 100% coverage of type-annotated functions.
  • Performance Testing Needs: Measure the impact of type checking on build times.

6. Documentation & Maintenance

6.1 Documentation Requirements

  • API Documentation Updates: None identified.
  • Architecture Documentation: Update documentation to reflect mypy configuration.
  • Configuration Changes: Ensure documentation includes running mypy.
  • Usage Examples: Provide examples of type annotations and how to configure mypy.

6.2 Maintenance Considerations

  • Long-term Maintainability: The introduction of type checking can help reduce technical debt by making the codebase more robust and easier to maintain.
  • Technical Debt Assessment: If type annotations are not kept up-to-date, this could introduce new forms of technical debt.
  • Monitoring Requirements: Monitor for type errors in the CI/CD pipeline.

7. Deployment & Operations

7.1 Deployment Impact

  • Deployment Strategy: Ensure deployment instructions include running mypy.
  • Rollback Plan: If the CI/CD pipeline fails due to type errors, roll back the changes and address the issues.
  • Configuration Changes: Update the CI/CD pipeline to include mypy checks.

7.2 Operational Considerations

  • Monitoring Requirements: Monitor the CI/CD pipeline for type errors.
  • Performance Metrics: Measure the impact of type checking on build times.
  • Resource Utilization: Increased CPU usage during the type checking process.

8. Summary & Recommendations

8.1 Key Action Items

  1. Fix type annotations: High priority, estimated 2 hours.
  2. Update CI/CD pipeline: Medium priority, estimated 1 hour.
  3. Standardize type annotations: Low priority, estimated 1 hour.

8.2 Future Considerations

  • Long-term Improvements: Gradually introduce type annotations and monitor the CI/CD pipeline for issues.
  • Technical Debt Items: Keep type annotations up-to-date to avoid introducing new forms of technical debt.
  • Scalability Considerations: No significant scalability considerations identified.

@amotl
Copy link
Collaborator Author

amotl commented Oct 30, 2024

Thanks for your review, @llamapreview. JFYI: You got the Markdown formatting wrong, by posting your comment with a ```markdown preamble:

image

The result is that:

image

@amotl amotl merged commit a5b6d36 into main Oct 30, 2024
34 of 35 checks passed
@amotl amotl deleted the mypy branch October 30, 2024 22:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
python Pull requests that update Python code qa and testing Quality assurance and software testing question Further information is requested
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant