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

Support external aiohttp session #41

Merged
merged 2 commits into from
Jan 19, 2025
Merged

Support external aiohttp session #41

merged 2 commits into from
Jan 19, 2025

Conversation

tjorim
Copy link
Owner

@tjorim tjorim commented Jan 19, 2025

Enhance iRail API client to support external aiohttp session management and add ETag cache clearing method

Summary by CodeRabbit

  • New Features

    • Added option to pass an existing client session during initialization.
    • Introduced method to clear ETag cache.
  • Improvements

    • Enhanced session management and handling.
    • Improved logging for session-related operations.

Copy link
Contributor

coderabbitai bot commented Jan 19, 2025

📝 Walkthrough

Walkthrough

The changes to the pyrail/irail.py file enhance the iRail class's session management capabilities. The modifications introduce an optional session parameter during initialization, allowing users to pass an existing ClientSession. A new _owns_session attribute tracks session ownership, and the __init__, __aenter__, and __aexit__ methods have been updated to handle session lifecycle more flexibly. A new clear_etag_cache method provides additional cache management functionality.

Changes

File Change Summary
pyrail/irail.py - Added optional session parameter to __init__ method
- Introduced _owns_session attribute for session management
- Updated __aenter__ and __aexit__ methods to handle session lifecycle
- Added clear_etag_cache() method to reset ETag cache

Sequence Diagram

sequenceDiagram
    participant User
    participant iRail
    participant ClientSession
    
    alt New Session
        User->>iRail: Initialize with no session
        iRail->>ClientSession: Create new session
        iRail-->>User: iRail instance
    else Existing Session
        User->>iRail: Initialize with existing session
        iRail->>ClientSession: Use provided session
        iRail-->>User: iRail instance
    end
    
    User->>iRail: clear_etag_cache()
    iRail-->>User: Cache cleared
Loading

Poem

🐰 A Rabbit's Ode to Session Delight 🚂

In PyRail's realm, a session so bright,
Flexible, nimble, with caching's might
Passed or created, it dances with glee
Clearing the cache, setting data free!
A rabbit's code, elegant and light 🌟

Finishing Touches

  • 📝 Generate Docstrings (Beta)

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 generate docstrings to generate docstrings for this PR. (Beta)
  • @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.

Copy link
Contributor

@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: 0

🧹 Nitpick comments (5)
pyrail/irail.py (5)

57-57: Consider using a more precise type annotation for the session parameter.

The type annotation could be more specific by using aiohttp.ClientSession instead of just ClientSession to make the expected type clearer.

-def __init__(self, lang: str = "en", session: ClientSession | None = None) -> None:
+def __init__(self, lang: str = "en", session: aiohttp.ClientSession | None = None) -> None:

Also applies to: 62-62


70-71: Document session ownership behavior.

The session ownership tracking is implemented correctly, but it would be helpful to document this behavior in the class docstring to make it clear to users how session lifecycle management works.

Add this to the class docstring:

    """A Python wrapper for the iRail API, handling rate limiting and endpoint requests.

    Attributes:
        lang (str): The language for API responses ('nl', 'fr', 'en', 'de').
+       session (aiohttp.ClientSession | None): The HTTP session used for API requests.
+           If not provided, a new session will be created and managed internally.
+           If provided, the session lifecycle must be managed externally.

    Endpoints:

76-81: Enhance session initialization logging.

The logging messages could be more descriptive to help with debugging session-related issues.

-            logger.debug("Using externally provided session")
+            logger.debug("Using externally provided session %r", self.session)
-            logger.debug("Initializing new aiohttp session")
+            logger.debug("Creating new internal aiohttp session")

89-98: Improve error handling specificity in session cleanup.

The current error handling catches all exceptions, but it would be better to catch specific aiohttp exceptions.

                 try:
                     await self.session.close()
-                except Exception as e:
+                except (aiohttp.ClientError, asyncio.CancelledError) as e:
                     logger.error("Error while closing aiohttp session: %s", e)

124-128: Add return type annotation and adjust logging level.

The method should have a return type annotation, and the logging level should be INFO since cache clearing is a significant operation.

-    def clear_etag_cache(self):
+    def clear_etag_cache(self) -> None:
         """Clear the ETag cache."""
         self.etag_cache.clear()
-        logger.debug("ETag cache cleared")
+        logger.info("ETag cache cleared")
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e25c390 and 78ece22.

📒 Files selected for processing (1)
  • pyrail/irail.py (2 hunks)

Copy link
Contributor

@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: 0

🧹 Nitpick comments (3)
pyrail/irail.py (3)

60-60: Consider using newer type annotation syntax.

The implementation is correct, but the type annotation could be modernized.

-def __init__(self, lang: str = "en", session: ClientSession | None = None) -> None:
+def __init__(self, lang: str = "en", session: None | ClientSession = None) -> None:

Note: In Python, it's a convention to put None first in Union types with None. This makes it clearer that the parameter is optional and matches the pattern used in the standard library.

Also applies to: 65-65, 73-74


79-84: Consider simplifying session closure logic.

While the implementation is correct, the session closure logic in __aexit__ could be more concise.

 async def __aexit__(
     self, exc_type: Type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None
 ) -> None:
     """Close the aiohttp client session when exiting the async context."""
     if self.session:
-        if self.session.closed:
-            logger.debug("Session is already closed, skipping closure")
-        elif not self._owns_session:
-            logger.debug("Session is externally provided; not closing it")
-        else:
-            logger.debug("Closing aiohttp session")
-            try:
-                await self.session.close()
-            except Exception as e:
-                logger.error("Error while closing aiohttp session: %s", e)
+        if self._owns_session and not self.session.closed:
+            logger.debug("Closing aiohttp session")
+            try:
+                await self.session.close()
+            except Exception as e:
+                logger.error("Error while closing aiohttp session: %s", e)
+        else:
+            logger.debug(
+                "Session is %s, skipping closure",
+                "already closed" if self.session.closed else "externally provided"
+            )

Also applies to: 92-101


127-131: Enhance method documentation.

While the implementation is correct, the method documentation could be more descriptive.

     def clear_etag_cache(self) -> None:
-        """Clear the ETag cache."""
+        """Clear the ETag cache used for conditional requests.
+
+        This method removes all cached ETags, forcing subsequent requests to fetch fresh data
+        instead of receiving 304 Not Modified responses.
+
+        Example:
+            async with iRail() as client:
+                await client.get_stations()  # Response cached with ETag
+                client.clear_etag_cache()    # Clear the cache
+                await client.get_stations()  # Fresh data fetched
+        """
         self.etag_cache.clear()
         logger.info("ETag cache cleared")
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 78ece22 and b10e4c7.

📒 Files selected for processing (1)
  • pyrail/irail.py (3 hunks)
🔇 Additional comments (2)
pyrail/irail.py (2)

31-33: LGTM! Well-documented session attribute.

The documentation clearly explains the session parameter's optionality and lifecycle management responsibilities.


31-33: Verify proper session cleanup in documentation.

While the implementation correctly handles session lifecycle, consider adding a warning in the documentation about the importance of proper cleanup when using external sessions.

         session (ClientSession, optional): The HTTP session used for API requests.
             If not provided, a new session will be created and managed internally.
             If provided, the session lifecycle must be managed externally.
+            Warning: When providing an external session, ensure it's properly closed
+            to prevent resource leaks. The session should be active when entering
+            the context and remain open until after exiting the context.

Let's verify the session management implementation:

Also applies to: 60-60, 73-74

@tjorim tjorim merged commit 8ae3b05 into main Jan 19, 2025
6 checks passed
@tjorim tjorim deleted the support-passing-websession branch January 19, 2025 09:05
@tjorim
Copy link
Owner Author

tjorim commented Jan 19, 2025

@coderabbitai Do you propose we update the README as well?

Copy link
Contributor

coderabbitai bot commented Jan 19, 2025

Tip

For best results, initiate chat on the files or code changes.

@tjorim Yes, I recommend updating the README to document the new features. Here's a suggested addition to include under the "Usage" section:

### Session Management
You can provide an external aiohttp ClientSession:

```python
from aiohttp import ClientSession

async def main():
    # Using an external session
    async with ClientSession() as session:
        async with iRail(session=session) as api:
            stations = await api.get_stations()
            print(f"Total stations: {len(stations)}")

    # Or let iRail manage its own session
    async with iRail() as api:
        stations = await api.get_stations()

Cache Management

You can clear the ETag cache when needed:

async with iRail() as api:
    # Clear the ETag cache
    api.clear_etag_cache()
    # Subsequent requests will fetch fresh data
    stations = await api.get_stations()

This addition would help users understand and utilize the new features effectively. Would you like me to propose these changes?

<details>
<summary>🔗 Analysis chain</summary>





</details>

<details>
<summary>🏁 Scripts executed</summary>

The following scripts were executed for the analysis:

Script:

#!/bin/bash

Check if README exists and show its content

cat README.md 2>/dev/null || echo "README.md not found"


Length of output: 3383

</details>

@tjorim
Copy link
Owner Author

tjorim commented Jan 27, 2025

@coderabbitai Thanks for your feedback, I implemented these changes in #42.

Copy link
Contributor

coderabbitai bot commented Jan 27, 2025

Tip

For best results, initiate chat on the files or code changes.

@tjorim Thank you for implementing the documentation changes! Looking forward to seeing the improved README in PR #42.

(_/)
(^.^)
(")_(")
happy bunny


If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!

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