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

feat: integrate dependency injection with kink library #859

Merged
merged 5 commits into from
Nov 5, 2024

Conversation

iPromKnight
Copy link
Collaborator

@iPromKnight iPromKnight commented Nov 5, 2024

  • Added dependency injection using the kink library to manage API instances and service initialization.
  • Updated various modules to utilize dependency injection for better modularity and testability.
  • Refactored API initialization and validation logic to be more centralized and consistent.
  • Enhanced Trakt, Plex, Overseerr, Mdblist, and Listrr services to use injected dependencies.
  • Updated CLI and service modules to align with the new dependency injection approach.
  • Modified pyproject.toml to include kink as a dependency.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for the kink dependency to enhance API management through dependency injection.
    • Introduced new methods for managing RSS URLs in the PlexAPI.
    • Enhanced OAuth capabilities in the TraktAPI with new methods for authorization flow.
  • Improvements

    • Streamlined API initialization and validation processes across multiple services.
    • Improved error handling and logging for database operations and API interactions.
  • Bug Fixes

    • Resolved issues related to API instantiation and validation in various content services.

These updates enhance the overall functionality and reliability of the application, providing a more modular and maintainable codebase.

Copy link
Contributor

coderabbitai bot commented Nov 5, 2024

Warning

Rate limit exceeded

@iPromKnight has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 0 minutes and 9 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 8a8509a and 21edfd3.

Walkthrough

The pull request introduces several changes across multiple files, primarily focusing on the integration of a new dependency, kink, for dependency injection. The pyproject.toml file is updated to include kink as a dependency. Various API classes, including ListrrAPI, TraktAPI, PlexAPI, OverseerrAPI, and MdblistAPI, have been modified to utilize dependency injection for instance management. Additionally, several classes related to media item management and database operations have been enhanced with improved error handling and validation logic.

Changes

File Path Change Summary
pyproject.toml Added dependency: kink = "^0.8.1" under [tool.poetry.dependencies].
src/program/apis/__init__.py Added several API setup functions and imports for various APIs; defined bootstrap_apis function.
src/program/apis/listrr_api.py Updated trakt_api initialization to use dependency injection.
src/program/apis/overseerr_api.py Updated trakt_api initialization to use dependency injection.
src/program/apis/plex_api.py Updated constructor to remove rss_urls parameter; added set_rss_urls and clear_rss_urls methods.
src/program/apis/trakt_api.py Updated constructor to accept new OAuth parameters; added methods for OAuth flow handling.
src/program/db/db_functions.py Enhanced error handling and refined deletion logic for media items.
src/program/program.py Renamed initialize_services to initialize_apis, which now calls bootstrap_apis().
src/program/services/content/listrr.py Updated api initialization to use dependency injection; modified validate method for error handling.
src/program/services/content/mdblist.py Changed api initialization to use dependency injection; updated validate method accordingly.
src/program/services/content/overseerr.py Updated api initialization to use dependency injection in validate method.
src/program/services/content/plex_watchlist.py Updated api initialization to use dependency injection in validate method.
src/program/services/content/trakt.py Removed TraktOAuthRequestHandler class and related methods; updated TraktContent to use dependency injection.
src/program/services/indexers/trakt.py Changed api initialization to use dependency injection; improved error logging in run method.
src/program/services/updaters/plex.py Updated api initialization to use dependency injection; modified validate method for error handling.
src/routers/secure/default.py Updated OAuth handling to use dependency injection for TraktAPI.
src/routers/secure/webhooks.py Changed TraktAPI instantiation to use dependency injection.

Poem

🐇 In the burrow, changes bloom,
New dependencies sweep the room.
APIs now dance with grace,
Kink brings a fresh embrace.
With each hop, our code does grow,
A joyful leap, let’s go, go, go! 🌼


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.

- Added dependency injection using the kink library to manage API instances and service initialization.
- Updated various modules to utilize dependency injection for better modularity and testability.
- Refactored API initialization and validation logic to be more centralized and consistent.
- Enhanced Trakt, Plex, Overseerr, Mdblist, and Listrr services to use injected dependencies.
- Updated CLI and service modules to align with the new dependency injection approach.
- Modified pyproject.toml to include kink as a dependency.

# Conflicts:
#	src/program/db/db_functions.py
#	src/program/utils/cli.py
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: 17

🧹 Outside diff range and nitpick comments (24)
src/program/apis/__init__.py (2)

1-7: Add __all__ declaration to explicitly define public API.

To improve module interface clarity and address the unused imports warning, consider adding an __all__ declaration at the top of the file.

+__all__ = [
+    'ListrrAPI', 'ListrrAPIError',
+    'TraktAPI', 'TraktAPIError',
+    'PlexAPI', 'PlexAPIError',
+    'OverseerrAPI', 'OverseerrAPIError',
+    'MdblistAPI', 'MdblistAPIError',
+    'bootstrap_apis'
+]

from .listrr_api import ListrrAPI, ListrrAPIError
🧰 Tools
🪛 Ruff

1-1: .listrr_api.ListrrAPIError imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


2-2: .trakt_api.TraktAPIError imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


3-3: .plex_api.PlexAPIError imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


4-4: .overseerr_api.OverseerrAPIError imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


5-5: .mdblist_api.MdblistAPIError imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


9-41: Consider implementing a more robust dependency injection pattern.

The current implementation could benefit from a more structured DI approach:

  1. Use a service provider pattern to manage API lifecycle
  2. Consider implementing interface abstractions for better testability
  3. Add proper cleanup/disposal of API instances

Would you like me to provide an example implementation of a more robust DI pattern?

src/program/services/content/overseerr.py (3)

18-18: Consider adding runtime safety checks for the api attribute

While initializing self.api to None is appropriate for DI, consider adding runtime checks in methods that use self.api to prevent potential NullPointerException if validate() hasn't been called or failed.

Example safety check:

 def run(self):
     """Fetch new media from `Overseerr`"""
+    if not self.initialized or self.api is None:
+        logger.error("Overseerr API not initialized. Please ensure validate() was called and succeeded.")
+        return
     if self.settings.use_webhook and self.run_once:
         return

32-32: Consider adding DI container validation

While the DI implementation is correct, consider adding a check to verify that OverseerrAPI is registered in the DI container to provide a more specific error message.

Example implementation:

 def validate(self) -> bool:
     if not self.settings.enabled:
         return False
     if self.settings.api_key == "" or len(self.settings.api_key) != 68:
         logger.error("Overseerr api key is not set.")
         return False
     try:
+        if OverseerrAPI not in di:
+            logger.error("OverseerrAPI not registered in dependency injection container")
+            return False
         self.api = di[OverseerrAPI]

Line range hint 38-40: Enhance connection error logging

Consider including the configured URL in the connection error log to aid in troubleshooting.

-            logger.error("Overseerr URL is not reachable, or it timed out")
+            logger.error(f"Overseerr URL '{self.settings.url}' is not reachable, or it timed out")
src/program/services/content/mdblist.py (1)

17-17: Consider adding safeguards against None access

While initializing self.api as None is consistent with the DI pattern, consider adding runtime checks to prevent potential NoneType errors if methods are called before validate().

 def run(self) -> Generator[MediaItem, None, None]:
+    if self.api is None:
+        logger.error("Mdblist API not initialized. Ensure validate() is called first.")
+        return
     items_to_yield = []
src/program/services/content/listrr.py (3)

Line range hint 16-20: Consider refactoring the initialization pattern.

While the change to initialize api as None is good for dependency injection, the early return in the constructor is a code smell that could make the object's state unclear. Consider throwing an exception or using a factory pattern instead.

 def __init__(self):
     self.key = "listrr"
     self.settings = settings_manager.settings.content.listrr
     self.api = None
-    self.initialized = self.validate()
-    if not self.initialized:
-        return
-    logger.success("Listrr initialized!")
+    if not self.validate():
+        raise ValueError("Listrr initialization failed")
+    logger.success("Listrr initialized!")

Line range hint 44-52: Consider separating validation concerns.

The validate method is handling multiple responsibilities: settings validation, dependency injection, and API validation. Consider splitting these into separate methods for better maintainability and testing.

+    def _validate_settings(self) -> bool:
+        """Validate Listrr settings configuration."""
+        if not self.settings.enabled:
+            return False
+        # ... rest of the settings validation logic ...
+
+    def _initialize_api(self) -> bool:
+        """Initialize and validate API connection."""
+        try:
+            self.api = di[ListrrAPI]
+            response = self.api.validate()
+            if not response.is_ok:
+                logger.error(
+                    f"Listrr ping failed - Status Code: {response.status_code}, Reason: {response.response.reason}",
+                )
+            return response.is_ok
+        except Exception as e:
+            logger.error(f"Listrr ping exception: {e}")
+            return False
+
     def validate(self) -> bool:
-        """Validate Listrr settings."""
-        if not self.settings.enabled:
-            return False
-        # ... rest of the method ...
-        try:
-            self.api = di[ListrrAPI]
-            response = self.api.validate()
-            if not response.is_ok:
-                logger.error(
-                    f"Listrr ping failed - Status Code: {response.status_code}, Reason: {response.response.reason}",
-                )
-            return response.is_ok
-        except Exception as e:
-            logger.error(f"Listrr ping exception: {e}")
-            return False
+        """Validate settings and initialize API."""
+        return self._validate_settings() and self._initialize_api()

Line range hint 54-67: Improve error handling in run method.

The current error handling silently returns None on failure, which could lead to unexpected behavior. Consider:

  1. Using more specific exception types
  2. Yielding an empty list instead of None
  3. Adding logging for successful operations
     def run(self) -> Generator[MediaItem, None, None]:
         """Fetch new media from `Listrr`"""
         try:
             movie_items = self.api.get_items_from_Listrr("Movies", self.settings.movie_lists)
             show_items = self.api.get_items_from_Listrr("Shows", self.settings.show_lists)
-        except Exception as e:
+        except (ConnectionError, ValueError, AttributeError) as e:
             logger.error(f"Failed to fetch items from Listrr: {e}")
-            return
+            yield []
+            return

         imdb_ids = movie_items + show_items
         listrr_items = [MediaItem({"imdb_id": imdb_id, "requested_by": self.api.key}) for imdb_id in imdb_ids if imdb_id.startswith("tt")]
         logger.info(f"Fetched {len(listrr_items)} items from Listrr")
+        logger.debug(f"Successfully processed Listrr items")
         yield listrr_items
src/program/services/content/plex_watchlist.py (3)

30-30: Add type hint for dependency injection

Add a type hint to make the code more maintainable and IDE-friendly.

-            self.api = di[PlexAPI]
+            self.api: PlexAPI = di[PlexAPI]

30-34: Refine error handling

The broad Exception catch could mask specific issues. Consider catching more specific exceptions.

         try:
             self.api = di[PlexAPI]
             self.api.validate_account()
-        except Exception as e:
-            logger.error(f"Unable to authenticate Plex account: {e}")
+        except KeyError as e:
+            logger.error("PlexAPI not registered in dependency injection container")
+            return False
+        except ConnectionError as e:
+            logger.error(f"Connection error while authenticating Plex account: {e}")
+            return False
+        except Exception as e:
+            logger.error(f"Unexpected error while authenticating Plex account: {e}")
             return False

Line range hint 23-52: Consider splitting validation logic

The validate method has multiple responsibilities and high cyclomatic complexity. Consider splitting it into smaller, focused methods:

  • _validate_settings()
  • _validate_plex_account()
  • _validate_rss_feeds()

Would you like me to provide a refactored implementation?

src/program/apis/listrr_api.py (1)

Line range hint 33-71: Consider refactoring complex method for better maintainability.

The get_items_from_Listrr method is marked with noqa: C901, PLR0912 indicating high complexity. Consider breaking it down into smaller, focused functions:

  1. List validation logic
  2. Item fetching logic
  3. ID resolution logic (TMDB → IMDB)

Here's a suggested refactoring approach:

def _validate_list_id(self, list_id: str) -> bool:
    return bool(list_id and len(list_id) == 24)

def _fetch_page_items(self, content_type: str, list_id: str, page: int) -> tuple[list, int]:
    url = f"api/List/{content_type}/{list_id}/ReleaseDate/Descending/{page}"
    response = self.request_handler.execute(HttpMethod.GET, url)
    return response.data.get("items", []), response.data.get("pages", 1)

def _resolve_item_id(self, item: dict, content_type: str) -> str | None:
    if imdb_id := item.get("imDbId"):
        return imdb_id
    if content_type == "Movies" and (tmdb_id := item.get("tmDbId")):
        return self.trakt_api.get_imdbid_from_tmdb(tmdb_id)
    return None

def get_items_from_Listrr(self, content_type: str, content_lists: list[str]) -> list[str]:
    unique_ids: set[str] = set()
    if not content_lists:
        return list(unique_ids)

    for list_id in content_lists:
        if not self._validate_list_id(list_id):
            continue

        page, total_pages = 1, 1
        while page <= total_pages:
            try:
                items, total_pages = self._fetch_page_items(content_type, list_id, page)
                for item in items:
                    if item_id := self._resolve_item_id(item, content_type):
                        unique_ids.add(item_id)
            except HTTPError as e:
                if e.response.status_code in [400, 404, 429, 500]:
                    break
            except Exception as e:
                logger.error(f"An error occurred: {e}")
                break
            page += 1

    return list(unique_ids)
src/program/services/indexers/trakt.py (4)

Line range hint 31-47: Add type hints and consider recursion depth

The copy_items method handles complex nested structures but lacks complete type hints. Also, deep recursion for large shows could be problematic.

Consider these improvements:

-    def copy_items(self, itema: MediaItem, itemb: MediaItem):
+    def copy_items(self, itema: MediaItem, itemb: MediaItem) -> MediaItem:
         """Copy attributes from itema to itemb recursively.
         
         Args:
             itema: Source media item
             itemb: Target media item
         Returns:
             MediaItem: Updated target item
         """

Consider implementing an iterative approach for large shows to prevent stack overflow:

def copy_items(self, itema: MediaItem, itemb: MediaItem) -> MediaItem:
    if not isinstance(itemb, (Movie, Show)):
        raise ValueError(f"Unexpected item type: {type(itemb)}")
    
    is_anime = itema.is_anime or itemb.is_anime
    
    if isinstance(itemb, Movie):
        self.copy_attributes(itema, itemb)
        itemb.set("is_anime", is_anime)
        return itemb
    
    # Handle Show type
    if isinstance(itemb, Show):
        season_map = {s.number: s for s in itemb.seasons}
        for source_season in itema.seasons:
            if target_season := season_map.get(source_season.number):
                episode_map = {e.number: e for e in target_season.episodes}
                for source_episode in source_season.episodes:
                    if target_episode := episode_map.get(source_episode.number):
                        self.copy_attributes(source_episode, target_episode)
                target_season.set("is_anime", is_anime)
        itemb.set("is_anime", is_anime)
    
    return itemb

Line range hint 91-95: Improve error handling specificity in should_submit

The generic exception handling could mask specific issues. Consider handling specific exceptions and providing more context in error messages.

     try:
         interval = timedelta(seconds=settings.update_interval)
         return datetime.now() - item.indexed_at > interval
-    except Exception:
-        logger.error(f"Failed to parse date: {item.indexed_at} with format: {interval}")
+    except (ValueError, TypeError) as e:
+        logger.error(f"Failed to parse date for item '{item.title}' (ID: {item.imdb_id}). "
+                    f"indexed_at: {item.indexed_at}, interval: {interval}. Error: {str(e)}")
         return False

Line range hint 108-114: Remove unnecessary empty lines at end of file

There are multiple empty lines at the end of the file that should be removed.

Keep only one empty line at the end of the file.


Line range hint 98-107: Enhance method documentation

The _add_seasons_to_show method would benefit from a more comprehensive docstring including parameter types and return value.

     def _add_seasons_to_show(self, show: Show, imdb_id: str):
-        """Add seasons to the given show using Trakt API."""
+        """Add seasons to the given show using Trakt API.
+        
+        Args:
+            show: Show object to add seasons to
+            imdb_id: IMDb ID of the show
+            
+        Note:
+            This method modifies the show object in place by adding seasons and episodes.
+            Seasons with number 0 (specials) are skipped.
+        """
src/program/apis/plex_api.py (1)

25-26: LGTM! Consider adding docstring for improved documentation.

The constructor changes align well with dependency injection principles by removing the direct dependency on RSS URLs. The initialization is safe and follows best practices.

Add a docstring to document the parameters and their types:

 def __init__(self, token: str, base_url: str):
+    """Initialize PlexAPI instance.
+    
+    Args:
+        token (str): Plex authentication token
+        base_url (str): Base URL for the Plex server
+    """
     self.rss_urls: Optional[List[str]] = None
src/program/apis/overseerr_api.py (1)

Line range hint 82-117: Consider adding specific error handling for DI resolution.

The get_imdb_id method uses the injected trakt_api but doesn't specifically handle DI resolution failures. Consider adding explicit error handling:

     def get_imdb_id(self, data) -> str | None:
         """Get imdbId for item from overseerr"""
+        try:
+            trakt_api = self.trakt_api  # Attempt to resolve DI
+        except Exception as e:
+            logger.error(f"Failed to resolve TraktAPI dependency: {str(e)}")
+            return None
+
         if data.mediaType == "show":
             external_id = data.tvdbId
             data.mediaType = "tv"
src/program/program.py (1)

71-72: Enhance service initialization documentation.

The docstring should provide more context about the initialization process and its dependencies.

Consider expanding the documentation:

 def initialize_services(self):
-    """Initialize all services."""
+    """Initialize all services.
+    
+    This method should be called after initialize_apis() to ensure all required
+    API dependencies are available. It sets up both requesting services
+    (Overseerr, PlexWatchlist, etc.) and processing services (TraktIndexer,
+    Scraping, etc.).
+    
+    Raises:
+        RuntimeError: If APIs are not properly initialized.
+    """
src/routers/secure/default.py (1)

117-117: Avoid passing trakt_api_key as a parameter to handle_oauth_callback

Since TraktAPI should encapsulate its own API key from the settings, passing trakt_api_key as a parameter is redundant. Access the API key within the TraktAPI class to streamline the interface and reduce potential errors.

Apply this change:

-   success = trakt_api.handle_oauth_callback(trakt_api_key, code)
+   success = trakt_api.handle_oauth_callback(code)

And modify the handle_oauth_callback method in TraktAPI accordingly to use the internally stored API key.

src/program/db/db_functions.py (3)

Line range hint 41-54: Add type hints to function parameters in get_item_by_id.

Consider adding type hints to the item_types and session parameters in the get_item_by_id function to enhance code clarity and facilitate static type checking.

Apply this diff to update the function signature:

-def get_item_by_id(item_id: str, item_types = None, session = None):
+def get_item_by_id(item_id: str, item_types: list = None, session: Session = None):
🧰 Tools
🪛 Ruff

13-13: Redefinition of unused root_dir from line 10

Remove definition: root_dir

(F811)


Line range hint 56-61: Add type hints to function parameters in get_items_by_ids.

The get_items_by_ids function would benefit from specifying type hints for the ids, item_types, and session parameters. This improves code maintainability and assists with static analysis.

Apply this diff to update the function signature:

-def get_items_by_ids(ids: list, item_types = None, session = None):
+def get_items_by_ids(ids: list[str], item_types: list = None, session: Session = None):
🧰 Tools
🪛 Ruff

13-13: Redefinition of unused root_dir from line 10

Remove definition: root_dir

(F811)


Line range hint 116-150: Specify type hint for session parameter in delete_seasons_and_episodes.

Adding a type hint for the session parameter in the delete_seasons_and_episodes function enhances code readability and ensures consistency across the codebase.

Apply this diff to update the function signature:

-def delete_seasons_and_episodes(session, season_ids: list[str], batch_size: int = 30):
+def delete_seasons_and_episodes(session: Session, season_ids: list[str], batch_size: int = 30):
🧰 Tools
🪛 Ruff

13-13: Redefinition of unused root_dir from line 10

Remove definition: root_dir

(F811)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c80f609 and 8a8509a.

⛔ Files ignored due to path filters (1)
  • poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • pyproject.toml (1 hunks)
  • src/program/apis/__init__.py (1 hunks)
  • src/program/apis/listrr_api.py (2 hunks)
  • src/program/apis/overseerr_api.py (2 hunks)
  • src/program/apis/plex_api.py (2 hunks)
  • src/program/apis/trakt_api.py (4 hunks)
  • src/program/db/db_functions.py (1 hunks)
  • src/program/program.py (3 hunks)
  • src/program/services/content/listrr.py (3 hunks)
  • src/program/services/content/mdblist.py (3 hunks)
  • src/program/services/content/overseerr.py (3 hunks)
  • src/program/services/content/plex_watchlist.py (3 hunks)
  • src/program/services/content/trakt.py (2 hunks)
  • src/program/services/indexers/trakt.py (2 hunks)
  • src/program/services/updaters/plex.py (3 hunks)
  • src/routers/secure/default.py (2 hunks)
  • src/routers/secure/webhooks.py (2 hunks)
🧰 Additional context used
🪛 Ruff
src/program/apis/__init__.py

1-1: .listrr_api.ListrrAPIError imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


2-2: .trakt_api.TraktAPIError imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


3-3: .plex_api.PlexAPIError imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


4-4: .overseerr_api.OverseerrAPIError imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)


5-5: .mdblist_api.MdblistAPIError imported but unused; consider removing, adding to __all__, or using a redundant alias

(F401)

src/program/db/db_functions.py

13-13: Redefinition of unused root_dir from line 10

Remove definition: root_dir

(F811)

🔇 Additional comments (28)
src/program/apis/__init__.py (1)

9-41: Verify API usage across the codebase.

Let's verify how these APIs are used to ensure the new DI pattern won't cause breaking changes.

✅ Verification successful

Let me gather more information about the API usage patterns to ensure we have a complete picture.


DI implementation is consistent and safe across the codebase

The verification shows that the dependency injection pattern is already properly implemented:

  • All API instances are correctly obtained through the DI container (di[API]) in services and routers
  • No direct instantiation of APIs exists outside of the bootstrap code
  • API imports are only used for type hints
  • The initialization order in bootstrap_apis() is correct, considering that some APIs depend on others (e.g., ListrrAPI and OverseerrAPI depend on TraktAPI)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check API usage patterns across the codebase

# Check direct API instantiation that might break with DI
echo "Checking direct API instantiations..."
rg -l "= (?:Listrr|Trakt|Plex|Overseerr|Mdblist)API\(" --type py

# Check existing error handling patterns
echo "Checking error handling patterns..."
rg -l "(?:Listrr|Trakt|Plex|Overseerr|Mdblist)APIError" --type py

# Check settings usage patterns
echo "Checking settings access patterns..."
rg -l "settings\.(?:updaters|content)\." --type py

Length of output: 1151


Script:

#!/bin/bash
# Check how these APIs are actually used in the services
echo "Checking API usage in services..."
rg -A 3 "(?:Listrr|Trakt|Plex|Overseerr|Mdblist)API" src/program/services/ --type py

# Check if there are any direct imports of these APIs in services
echo "Checking API imports in services..."
rg "^from.*import.*(?:Listrr|Trakt|Plex|Overseerr|Mdblist)API" src/program/services/ --type py

# Check for dependency injection container usage
echo "Checking DI container usage..."
rg "di\[.*API\]" --type py

Length of output: 6574

src/program/services/content/overseerr.py (2)

6-6: LGTM: Clean import addition for dependency injection

The kink import is properly placed with other third-party imports, aligning with the PR's objective to implement dependency injection.


32-32: Verify DI setup and dependency chain

Let's verify the DI setup and check for potential circular dependencies.

✅ Verification successful

Let me gather more information about the dependency chain and DI setup to make a conclusive verification.


DI setup and dependency chain verified successfully

The verification shows a clean dependency setup:

  • OverseerrAPI is properly registered in the DI container through bootstrap_apis()
  • The dependency chain is straightforward: Overseerr service -> OverseerrAPI -> TraktAPI
  • No circular dependencies detected
  • The DI usage in the service follows the established pattern
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check OverseerrAPI registration and its dependencies
echo "Checking OverseerrAPI registration and dependencies..."

# Look for OverseerrAPI registration in DI container setup
rg -l "di\[OverseerrAPI\]" --type py

# Check for potential circular dependencies by finding all files that import OverseerrAPI
echo "\nChecking for potential circular dependencies..."
rg -l "from.*overseerr_api import|import.*overseerr_api" --type py

# Look for OverseerrAPI class definition to understand its dependencies
echo "\nChecking OverseerrAPI dependencies..."
rg -l "class OverseerrAPI" --type py

Length of output: 639


Script:

#!/bin/bash
# Check the DI registration in apis/__init__.py
echo "Checking DI registration in apis/__init__.py:"
rg -A 5 "OverseerrAPI" src/program/apis/__init__.py

# Check the OverseerrAPI class implementation
echo "\nChecking OverseerrAPI implementation:"
rg -A 10 "class OverseerrAPI" src/program/apis/overseerr_api.py

# Check the usage in overseerr.py service
echo "\nChecking usage in overseerr service:"
rg -A 5 "OverseerrAPI" src/program/services/content/overseerr.py

Length of output: 2557

src/routers/secure/webhooks.py (2)

11-11: LGTM: Clean import addition

The kink dependency import is correctly placed and necessary for the DI implementation.


54-54: Verify TraktAPI registration and configuration

The previous implementation used rate_limit=False. We need to ensure the DI-provided instance is configured correctly.

Let's verify the TraktAPI registration:

✅ Verification successful

Rate limiting is properly configured in TraktAPI initialization

The verification shows that the DI-provided TraktAPI instance is correctly configured with rate limiting. Looking at the implementation:

  1. The TraktAPI class now has built-in rate limiting through get_rate_limit_params(max_calls=1000, period=300)
  2. The DI registration in __setup_trakt() uses the default constructor which includes this rate limiting
  3. The old implementation with rate_limit=False has been removed in the git history
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check TraktAPI registration and configuration in the DI container

# Test 1: Find where TraktAPI is registered in the DI container
rg -l "di.register.*TraktAPI" 

# Test 2: Check if rate_limit parameter is configured
ast-grep --pattern 'di.register\(\s*TraktAPI.*rate_limit\s*=\s*False'

Length of output: 103


Script:

#!/bin/bash
# Let's try a broader search to find TraktAPI registration and configuration

# Find files containing TraktAPI
rg -l "TraktAPI"

# Look for TraktAPI class definition and initialization
ast-grep --pattern 'class TraktAPI'

# Look for any rate_limit configuration
rg -A 3 "rate_limit.*=.*False"

# Look for di registration patterns more broadly
rg -A 3 "di\.register"

Length of output: 374


Script:

#!/bin/bash
# Let's examine the TraktAPI implementation and its usage

# Check TraktAPI implementation
cat src/program/apis/trakt_api.py

# Look for di registration in the main initialization files
cat src/program/apis/__init__.py

# Check the previous implementation in git history
git log -p src/routers/secure/webhooks.py | grep -A 5 "trakt_api"

Length of output: 18852

src/program/services/content/mdblist.py (2)

5-5: LGTM: Clean dependency injection import

The kink dependency is correctly imported for dependency injection support.


33-33: Verify API registration in DI container

Let's verify that MdblistAPI is properly registered in the DI container during application bootstrap.

✅ Verification successful

MdblistAPI registration is properly implemented

The verification shows that MdblistAPI is correctly registered in the DI container. Found in src/program/apis/__init__.py:

di[MdblistAPI] = mdblistApi

This confirms that the API registration is properly handled during application bootstrap, making it available for injection in src/program/services/content/mdblist.py.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for MdblistAPI registration in bootstrap/initialization code
rg -l "MdblistAPI.*di\[" --type py

Length of output: 36


Script:

#!/bin/bash
# Let's try different search patterns to find MdblistAPI registration
# First, search for MdblistAPI class definition
rg "class MdblistAPI" --type py -A 5

# Then search for any imports of MdblistAPI
rg "from.*MdblistAPI|import.*MdblistAPI" --type py

# Finally search for any DI registration patterns
rg "di\[.*\].*=.*MdblistAPI" --type py

Length of output: 1164


Script:

#!/bin/bash
# Let's search for DI registration patterns in initialization/bootstrap files
# First, find potential initialization files
fd "init|config|bootstrap|setup|main|app" --type f --extension py

# Then search for any DI container setup or registration patterns
rg "di\[|di\.set|register|container|provider" --type py

Length of output: 10965

src/program/services/content/listrr.py (2)

3-3: LGTM: Clean import of dependency injection module.

The import of di from kink is properly placed and follows the project's import style.


44-44: Verify dependency injection setup.

Let's ensure that ListrrAPI is properly registered with the DI container and that the pattern is consistently used.

✅ Verification successful

Let me check the DI registration by looking at the content of src/program/apis/__init__.py:


Dependency injection is properly implemented

The verification shows that:

  • ListrrAPI is correctly registered in the DI container within src/program/apis/__init__.py
  • The registration is conditional based on the feature being enabled
  • The DI pattern is consistently used across related services
  • There are no unexpected direct instantiations outside the DI registration
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for proper DI registration and usage

# Look for ListrrAPI registration with the DI container
echo "Checking for ListrrAPI registration..."
rg -l "di\[ListrrAPI\]" --type py

# Look for similar DI patterns in related files
echo "Checking for consistent DI usage..."
rg -l "from kink import di" --type py

# Check for potential direct instantiations that should use DI
echo "Checking for potential direct instantiations..."
rg "ListrrAPI\(" --type py

Length of output: 988


Script:

#!/bin/bash
# Check the DI registration in __init__.py
echo "Checking ListrrAPI registration in __init__.py..."
rg -A 5 -B 5 "ListrrAPI" src/program/apis/__init__.py

# Check if ListrrAPI is properly imported
echo "Checking ListrrAPI imports..."
rg "^from.*ListrrAPI|^import.*ListrrAPI" --type py

Length of output: 995

src/program/services/content/plex_watchlist.py (2)

6-6: LGTM: Clean dependency injection implementation

The switch from direct instantiation to dependency injection improves testability and follows good DI practices.

Also applies to: 17-17


Line range hint 54-71: LGTM: Clean implementation with proper error handling

The run method effectively combines watchlist and RSS items, with good use of set operations for deduplication and proper error handling.

pyproject.toml (1)

43-43: Consider version constraints and library maturity.

While the addition of kink aligns with the PR objectives for implementing dependency injection, there are a few considerations:

  1. The caret version constraint ^0.8.1 allows updates to 0.8.x. Consider:
    • Pinning to exact version (0.8.1) for better reproducibility
    • Updating to latest 0.8.2 for bug fixes
  2. The library is pre-1.0.0, indicating potential API instability. Ensure:
    • Comprehensive test coverage for DI implementations
    • Monitoring of upstream changes
✅ Verification successful

Let me generate additional scripts to check for any existing dependency injection patterns and kink usage in the codebase:

The previous results show container usage but in a different context (media containers, test containers). We need to verify if there's any existing DI implementation or preparation for it before making a final recommendation about the kink version.


Version constraint is appropriate for this integration

The current version constraint ^0.8.1 is actually suitable for this integration because:

  1. The PR shows a comprehensive DI implementation with multiple from kink import di imports across the codebase, indicating a well-planned integration
  2. The project uses Poetry for dependency management which handles version resolution effectively
  3. While kink is pre-1.0.0, the 0.8.x series has been stable with only patch updates (current 0.8.2 vs 0.8.1)
  4. The caret (^) constraint is appropriate here as it allows patch updates while preventing breaking changes

No changes needed to the version constraint.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify kink version compatibility and usage
# Test 1: Check if kink is used in test files
rg -l "import kink|from kink" -g "test_*.py"

# Test 2: Check for any existing DI implementations
rg "Injectable|inject|container" -g "*.py"

Length of output: 6809


Script:

#!/bin/bash
# Check for any DI-related patterns or kink usage in Python files
rg -l "Inject|Provider|Container|Dependency" -g "*.py"

# Check for any existing dependency management or configuration setup
rg -l "dependency|dependencies|di setup|injection" -g "*.py"

# Look for any recent changes related to DI
git log -p | rg -i "dependency|injection|kink"

Length of output: 2066

src/program/apis/listrr_api.py (2)

3-3: LGTM: Clean import addition for dependency injection.

The kink import is properly placed following Python import conventions.


28-28: Verify TraktAPI configuration in DI container.

The change to use dependency injection is good, but we need to ensure the TraktAPI is properly configured in the DI container, especially regarding the rate_limit parameter that was previously set to False.

Let's verify the TraktAPI configuration:

✅ Verification successful

Based on the search results, I can now provide a final response:

Rate limiting is properly configured in TraktAPI initialization

The TraktAPI is configured with appropriate rate limiting in its constructor:

  • Rate limit is set to 1000 requests per 300 seconds (5 minutes) via rate_limit_params = get_rate_limit_params(max_calls=1000, period=300)
  • The API also uses caching with a 24-hour expiration via trakt_cache = get_cache_params("trakt", 86400)
  • Both rate limiting and caching are properly applied when creating the session: create_service_session(rate_limit_params=rate_limit_params, use_cache=True, cache_params=trakt_cache)

The change from direct instantiation to DI is safe and maintains proper rate limiting configuration.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check TraktAPI configuration in DI container setup
# Expected: Find where TraktAPI is registered with its configuration

# Look for TraktAPI registration in bootstrap or configuration files
rg -l "TraktAPI" | xargs rg -A 5 "di\[TraktAPI\]|register.*TraktAPI"

# Look for rate_limit configuration
rg -A 3 "rate_limit.*TraktAPI"

Length of output: 3683


Script:

#!/bin/bash
# Let's check the TraktAPI class definition and initialization
ast-grep --pattern 'class TraktAPI {
  $$$
}'

# Also check for any rate_limit related configuration in settings
rg -A 5 "trakt:|rate_limit" 

# Look for TraktAPI instantiation in the DI setup
rg -A 10 "__setup_trakt|TraktAPI\("

Length of output: 36708

src/program/services/indexers/trakt.py (1)

6-6: Verify TraktAPI registration in DI container

The switch to dependency injection looks good, but we should ensure that TraktAPI is properly registered in the DI container before it's used.

Also applies to: 22-22

✅ Verification successful

TraktAPI is properly registered in the DI container

The TraktAPI is correctly registered in the DI container through the bootstrap_apis() function in src/program/apis/__init__.py. Specifically, the __setup_trakt() function instantiates and registers TraktAPI using di[TraktAPI] = traktApi. The implementation is clean and follows dependency injection best practices.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if TraktAPI is registered in the DI container
# Expected: Find the registration of TraktAPI in the DI container setup

# Look for TraktAPI registration
rg -l "di\[TraktAPI\]" | grep -v "trakt.py"

# Look for di.register or similar registration patterns for TraktAPI
ast-grep --pattern 'di.register($_, TraktAPI)'

Length of output: 247


Script:

#!/bin/bash
# Let's check the content of program/apis/__init__.py which might contain the registration
cat src/program/apis/__init__.py

# Also check for any other potential registration patterns
ast-grep --pattern 'di.factory($_, TraktAPI)'

# Look for TraktAPI class definition and its registration
rg -A 5 "class TraktAPI"

Length of output: 2510

src/program/apis/plex_api.py (1)

46-52: Verify RSS URL management usage across the codebase.

Since the RSS URL management has been refactored, we should verify all callers are updated accordingly.

✅ Verification successful

RSS URL management changes are safely implemented

The verification shows that all RSS URL access patterns are consistent with the new implementation:

  • Direct access to rss_urls attribute is properly encapsulated within plex_api.py
  • The only caller of set_rss_urls() and get_items_from_rss() is plex_watchlist.py, which uses them correctly
  • PlexAPI constructor calls don't pass RSS URLs (they're set later via set_rss_urls())
  • The new clear_rss_urls() method provides a safe way to reset RSS state
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for direct access to rss_urls and RSS-related method calls

# Test 1: Check for direct access to rss_urls attribute
echo "Checking for direct access to rss_urls attribute..."
rg "\.rss_urls\s*[^=]*=" --type py

# Test 2: Check for RSS-related method calls
echo "Checking for RSS method usage patterns..."
rg "set_rss_urls|clear_rss_urls|get_items_from_rss" --type py -A 2

# Test 3: Check for potential missed RSS URL assignments in constructors
echo "Checking for potential constructor calls with RSS URLs..."
ast-grep --pattern 'PlexAPI($$$)'

Length of output: 1977

src/program/apis/overseerr_api.py (2)

6-6: LGTM: Clean dependency injection import.

The kink import is properly placed with other third-party imports.


30-30: Verify DI configuration for TraktAPI.

While the dependency injection implementation looks correct, there are a few important aspects to verify:

  1. Ensure TraktAPI is properly registered in the DI container with the correct rate_limit configuration
  2. Consider adding error handling for DI resolution failures

Let's verify the TraktAPI registration:

✅ Verification successful

Based on the search results, I can now provide a final response:

TraktAPI DI setup is properly configured with rate limiting

The TraktAPI is correctly registered in the DI container with appropriate rate limiting:

  1. TraktAPI is initialized in src/program/apis/__init__.py with di[TraktAPI] = traktApi
  2. The TraktAPI class itself has rate limiting configured with rate_limit_params = get_rate_limit_params(max_calls=1000, period=300) in its initialization
  3. The rate limiting configuration is consistent with Trakt's API guidelines
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check TraktAPI registration and configuration
echo "Checking TraktAPI registration and configuration..."

# Look for TraktAPI DI registration
rg -l "di\[TraktAPI\]" --type py

# Look for TraktAPI configuration/registration code
ast-grep --pattern 'di.register(TraktAPI, $$$)'

Length of output: 429


Script:

#!/bin/bash
# Let's check for TraktAPI initialization and configuration patterns
echo "Checking TraktAPI initialization patterns..."

# Look for TraktAPI class definition and initialization
ast-grep --pattern 'class TraktAPI'

# Look for rate_limit configuration
rg "rate_limit" -A 5 -B 5 --type py

# Check DI setup in main or config files
fd "main|config|di" --type py --exec rg -l "TraktAPI|register"

Length of output: 51828


Script:

#!/bin/bash
# Let's check for TraktAPI initialization and DI registration
echo "Checking TraktAPI initialization and DI setup..."

# Look for TraktAPI class definition and initialization
rg -A 10 "class TraktAPI" --type py

# Look for DI registration patterns
rg "di\[" -B 5 -A 5 --type py

# Check for dependency injection setup files
fd "di|container" --extension py --exec rg -l "register|TraktAPI"

Length of output: 13943

src/program/program.py (1)

22-22: LGTM: Import for dependency injection bootstrap.

The import is correctly placed and aligns with the PR's objective of implementing dependency injection.

src/program/services/updaters/plex.py (3)

10-10: Imported di from kink for dependency injection

The import statement correctly brings in the di container from the kink library, enabling dependency injection within the module.


24-24: Initialized self.api to None in the constructor

Setting self.api to None in the __init__ method defers the instantiation to the validate method, which aligns with the new dependency injection approach.


46-49: Instantiated self.api using the dependency injection container in validate

Retrieving PlexAPI from the di container within the validate method is appropriate. This ensures that the API instance is properly managed and initialized only when needed.

src/program/services/content/trakt.py (3)

6-6: Proper import for dependency injection

The import statement correctly imports di from the kink library, enabling dependency injection.


17-17: Injection of TraktAPI instance via dependency injection

Successfully retrieves the TraktAPI instance from the dependency injection container, enhancing modularity and testability.


174-174: Ensured method returns IMDb IDs

The addition of return imdb_ids ensures the _extract_imdb_ids_with_none_type method returns the list of IMDb IDs as intended.

src/routers/secure/default.py (2)

8-9: Imports added for dependency injection

The imports of di from kink and TraktAPI are necessary for implementing dependency injection using the kink library.


Line range hint 87-88: Definition of TraktOAuthInitiateResponse model

The TraktOAuthInitiateResponse Pydantic model correctly defines the auth_url field for the Trakt OAuth initiation response.

src/program/apis/trakt_api.py (1)

35-42: Verify all instantiations of TraktAPI are updated to the new constructor

The initialization of TraktAPI now requires oauth_client_id, oauth_client_secret, and oauth_redirect_uri. Ensure that all places where TraktAPI is instantiated have been updated to pass these parameters.

Run the following script to find all instantiations of TraktAPI:

Review the output to ensure all instances are updated to the new constructor signature.

src/program/apis/__init__.py Outdated Show resolved Hide resolved
src/program/apis/__init__.py Show resolved Hide resolved
src/program/apis/__init__.py Outdated Show resolved Hide resolved
src/routers/secure/webhooks.py Show resolved Hide resolved
src/program/services/content/mdblist.py Show resolved Hide resolved
src/program/apis/trakt_api.py Outdated Show resolved Hide resolved
src/program/apis/trakt_api.py Show resolved Hide resolved
src/program/apis/trakt_api.py Show resolved Hide resolved
src/program/apis/trakt_api.py Outdated Show resolved Hide resolved
src/program/db/db_functions.py Show resolved Hide resolved
Copy link
Collaborator

@Gaisberg Gaisberg left a comment

Choose a reason for hiding this comment

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

Kinky!

- Updated TraktAPI to accept settings via TraktModel, enabling OAuth configuration.
- Added OAuth flow methods to handle authorization and token exchange.
- Integrated TraktOauthModel into TraktModel for structured OAuth settings.
- Modified API bootstrap to pass settings to TraktAPI.
- Ensured backward compatibility with existing settings structure.
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.

2 participants