Skip to content

Conversation

@saleweaver
Copy link
Owner

@saleweaver saleweaver commented Feb 9, 2026

Summary by Sourcery

Fix module-level imports in sp_api.base to avoid asyncio-related import errors by lazily loading selected symbols at attribute access time.

Bug Fixes:

  • Prevent asyncio import errors by deferring imports of Client, AccessTokenClient, Credentials, and AuthorizationError in sp_api.base until they are first accessed.

Enhancements:

  • Introduce lazy attribute-based loading for selected exports in sp_api.base via a module-level getattr and TYPE_CHECKING-only imports to preserve type hints without eager imports.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Feb 9, 2026

Reviewer's Guide

Refactors sp_api.base’s top-level imports to avoid runtime asyncio-related import errors by moving selected imports behind TYPE_CHECKING and implementing lazy, backward-compatible attribute access via getattr for Client and auth-related classes.

Sequence diagram for lazy attribute access via getattr in sp_api.base

sequenceDiagram
    participant Importer
    participant sp_api_base as sp_api_base
    participant sp_api_base_client as sp_api_base_client
    participant sp_api_auth as sp_api_auth
    participant sp_api_auth_exceptions as sp_api_auth_exceptions

    Importer->>sp_api_base: from sp_api.base import Client
    activate sp_api_base
    sp_api_base-->>Importer: triggers __getattr__(Client)
    sp_api_base->>sp_api_base_client: import Client
    sp_api_base_client-->>sp_api_base: Client
    sp_api_base-->>Importer: Client (cached in globals)
    deactivate sp_api_base

    Importer->>sp_api_base: from sp_api.base import AccessTokenClient, Credentials
    activate sp_api_base
    sp_api_base-->>Importer: triggers __getattr__(AccessTokenClient)
    sp_api_base->>sp_api_auth: from sp_api.auth import AccessTokenClient, Credentials
    sp_api_auth-->>sp_api_base: AccessTokenClient, Credentials
    sp_api_base-->>Importer: AccessTokenClient (cached in globals)
    sp_api_base-->>Importer: Credentials (cached in globals)
    deactivate sp_api_base

    Importer->>sp_api_base: from sp_api.base import AuthorizationError
    activate sp_api_base
    sp_api_base-->>Importer: triggers __getattr__(AuthorizationError)
    sp_api_base->>sp_api_auth_exceptions: from sp_api.auth.exceptions import AuthorizationError
    sp_api_auth_exceptions-->>sp_api_base: AuthorizationError
    sp_api_base-->>Importer: AuthorizationError (cached in globals)
    deactivate sp_api_base
Loading

Flow diagram for getattr resolution in sp_api.base

flowchart TD
    A(__getattr__ called with name) --> B{Is name Client}
    B -->|Yes| C[Import Client from sp_api.base.client]
    C --> D[Set globals Client]
    D --> E[Return Client]

    B -->|No| F{Is name AccessTokenClient or Credentials}
    F -->|Yes| G[Import AccessTokenClient and Credentials from sp_api.auth]
    G --> H[Select requested export by name]
    H --> I[Set globals with selected export]
    I --> J[Return selected export]

    F -->|No| K{Is name AuthorizationError}
    K -->|Yes| L[Import AuthorizationError from sp_api.auth.exceptions]
    L --> M[Set globals AuthorizationError]
    M --> N[Return AuthorizationError]

    K -->|No| O[Raise AttributeError for unknown name]
Loading

File-Level Changes

Change Details Files
Replace eager top-level imports of Client and authentication classes with lazy, on-demand imports while preserving the existing public API.
  • Removed direct runtime imports of AccessTokenClient, Credentials, and AuthorizationError at module import time to prevent import-time side effects and asyncio-related errors.
  • Added TYPE_CHECKING-only imports for Client and authentication-related classes so type checkers and IDEs still see these names without executing the imports at runtime.
  • Implemented a module-level getattr that lazily imports and caches Client, AccessTokenClient, Credentials, and AuthorizationError when they are first accessed, maintaining backward compatibility for existing sp_api.base usage.
  • Ensured that missing attributes still raise AttributeError via the getattr fallback, matching normal Python attribute access semantics.
sp_api/base/__init__.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@saleweaver saleweaver merged commit 42b0709 into master Feb 9, 2026
4 of 6 checks passed
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • The __getattr__ implementation repeats similar import/assignment logic for each symbol; consider centralizing this into a single mapping dict (name -> import path) to reduce branching and make it easier to extend or modify in the future.
  • Since these attributes are now lazily imported, it might be safer to derive or validate __all__ from the same set of supported names used in __getattr__ to avoid drift if new exports are added or existing ones are renamed.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `__getattr__` implementation repeats similar import/assignment logic for each symbol; consider centralizing this into a single mapping dict (name -> import path) to reduce branching and make it easier to extend or modify in the future.
- Since these attributes are now lazily imported, it might be safer to derive or validate `__all__` from the same set of supported names used in `__getattr__` to avoid drift if new exports are added or existing ones are renamed.

## Individual Comments

### Comment 1
<location> `sp_api/base/__init__.py:101` </location>
<code_context>
 FulfillmentChannels = FulfillmentChannel
+
+
+def __getattr__(name):
+    if name == "Client":
+        from .client import Client
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring the lazy imports into a single mapping of names to loader callables so that `__getattr__` just looks up and caches values instead of handling multiple conditional branches.

You can keep the lazy-import behavior while reducing branching and duplication by centralizing the mapping of exported names to loader functions. This also helps keep `TYPE_CHECKING`, `__all__`, and `__getattr__` in sync.

For example:

```python
from typing import TYPE_CHECKING, Callable, Dict, Any

if TYPE_CHECKING:
    from .client import Client
    from sp_api.auth import AccessTokenClient, Credentials
    from sp_api.auth.exceptions import AuthorizationError

_LAZY_LOADERS: Dict[str, Callable[[], Any]] = {
    "Client": lambda: __import__(__name__ + ".client", fromlist=["Client"]).Client,
    "AccessTokenClient": lambda: __import__("sp_api.auth", fromlist=["AccessTokenClient"]).AccessTokenClient,
    "Credentials": lambda: __import__("sp_api.auth", fromlist=["Credentials"]).Credentials,
    "AuthorizationError": lambda: __import__("sp_api.auth.exceptions", fromlist=["AuthorizationError"]).AuthorizationError,
}

def __getattr__(name: str) -> Any:
    try:
        loader = _LAZY_LOADERS[name]
    except KeyError:
        raise AttributeError(f"module '{__name__}' has no attribute '{name}'") from None
    value = loader()
    globals()[name] = value  # cache
    return value
```

This simplifies:

- One place to list supported lazy symbols (`_LAZY_LOADERS`) instead of multiple `if` branches.
- `__getattr__` no longer mixes unrelated responsibilities; it just looks up a loader and caches.
- Adding/removing a symbol only requires touching `_LAZY_LOADERS`, and optionally `__all__` / `TYPE_CHECKING`, reducing risk of drift.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

FulfillmentChannels = FulfillmentChannel


def __getattr__(name):
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (complexity): Consider refactoring the lazy imports into a single mapping of names to loader callables so that __getattr__ just looks up and caches values instead of handling multiple conditional branches.

You can keep the lazy-import behavior while reducing branching and duplication by centralizing the mapping of exported names to loader functions. This also helps keep TYPE_CHECKING, __all__, and __getattr__ in sync.

For example:

from typing import TYPE_CHECKING, Callable, Dict, Any

if TYPE_CHECKING:
    from .client import Client
    from sp_api.auth import AccessTokenClient, Credentials
    from sp_api.auth.exceptions import AuthorizationError

_LAZY_LOADERS: Dict[str, Callable[[], Any]] = {
    "Client": lambda: __import__(__name__ + ".client", fromlist=["Client"]).Client,
    "AccessTokenClient": lambda: __import__("sp_api.auth", fromlist=["AccessTokenClient"]).AccessTokenClient,
    "Credentials": lambda: __import__("sp_api.auth", fromlist=["Credentials"]).Credentials,
    "AuthorizationError": lambda: __import__("sp_api.auth.exceptions", fromlist=["AuthorizationError"]).AuthorizationError,
}

def __getattr__(name: str) -> Any:
    try:
        loader = _LAZY_LOADERS[name]
    except KeyError:
        raise AttributeError(f"module '{__name__}' has no attribute '{name}'") from None
    value = loader()
    globals()[name] = value  # cache
    return value

This simplifies:

  • One place to list supported lazy symbols (_LAZY_LOADERS) instead of multiple if branches.
  • __getattr__ no longer mixes unrelated responsibilities; it just looks up a loader and caches.
  • Adding/removing a symbol only requires touching _LAZY_LOADERS, and optionally __all__ / TYPE_CHECKING, reducing risk of drift.

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