Skip to content

Extract shared AsyncClientBase to eliminate cross-module duplication #148

@btoron

Description

@btoron

Is your feature request related to a problem? Please describe.

Error handling code (_parse_error_response(), _handle_http_error()) and base properties (baseUrl, headers, config) are duplicated identically across 5 async modules:

  • ofsc/async_client/core/_base.py (~98 lines)
  • ofsc/async_client/metadata.py (~91 lines)
  • ofsc/async_client/capacity.py (~69 lines)
  • ofsc/async_client/statistics.py (~69 lines)
  • ofsc/async_client/oauth.py (~48 lines)

Additionally, Protocol type stubs are duplicated in core/resources.py and core/users.py.

Total duplicated code: ~400+ lines across modules. Any bug fix to error handling requires updating 5 locations.

Note: Issue #116 covers duplication within metadata.py using helper methods. This issue addresses duplication across all async modules.

Describe the solution you'd like

Create a shared AsyncClientBase class in ofsc/async_client/_base.py:

class AsyncClientBase:
    """Base class for all async API modules."""

    def __init__(self, config: OFSConfig, client: httpx.AsyncClient):
        self._config = config
        self._client = client

    @property
    def config(self) -> OFSConfig:
        return self._config

    @property
    def baseUrl(self) -> str:
        if self._config.baseURL is None:
            raise ValueError("Base URL is not configured")
        return self._config.baseURL

    @property
    def headers(self) -> dict:
        # Shared auth headers logic
        ...

    def _parse_error_response(self, response: httpx.Response) -> dict:
        # Single implementation
        ...

    def _handle_http_error(self, e: httpx.HTTPStatusError, context: str = "") -> None:
        # Single implementation with full exception mapping
        ...

All modules inherit from it:

  • AsyncOFSMetadata(AsyncClientBase)
  • AsyncOFSCapacity(AsyncClientBase)
  • AsyncOFSStatistics(AsyncClientBase)
  • AsyncOFSOauth2(AsyncClientBase)
  • Core base class inherits too

Also create a shared Protocol in _protocols.py to replace duplicated protocols in core/resources.py and core/users.py.

Describe alternatives you've considered

  • Mixin approach (less clean but doesn't require changing inheritance)
  • Keep duplication but add tests to verify consistency (doesn't solve the root cause)

Additional context

This is a prerequisite for maintainable growth. As more modules are added (Statistics, Collaboration, Parts Catalog), each would currently copy-paste another ~100 lines of boilerplate. With a base class, new modules start at ~0 lines of boilerplate.

Related: #116 (metadata-specific refactoring)

Metadata

Metadata

Assignees

No one assigned

    Labels

    asyncAsync client implementationenhancementNew feature or requestrefactoringCode refactoring and technical debt

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions