feat: add configurable JsonClient class for dependency injection

未关闭 适合新手
#19 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

评估

难度
2/5
预计耗时
1-3 小时
新手友好度
76/100
Issue 类型
功能
描述清晰度
描述清楚
活跃度
冷清
技术栈
python
领域
api

调研方向

从 campus_python/init.py 开始,重点查看 Campus 类及其 auth 和 api 属性。然后检查 tests/flask_test/campus_request.py 以确认注入的客户端形态,并验证这两个属性都使用已配置的类,同时默认值仍为 CampusRequest;文档示例和小版本发布被列为额外的检查清单项目。

由索引模型根据 Issue 内容生成。

描述

Feature Request: Configurable JsonClient Class for Dependency Injection

Problem

When testing Campus services that use campus_python.Campus, we need to replace the default CampusRequest with a test-compatible version that routes to Flask test clients instead of making real HTTP requests.

Current Workaround: Monkey-Patching

Currently, we have to monkey-patch the CampusRequest class:

import campus_python
from tests.flask_test import TestCampusRequest

# Replace CampusRequest globally
campus_python.json_client.CampusRequest = TestCampusRequest
campus_python.CampusRequest = TestCampusRequest  # Also patch module reference

Problems with this approach:

  • ❌ Fragile - requires patching multiple module-level references
  • ❌ Hard to debug - changes global state
  • ❌ Confusing - not obvious that CampusRequest has been replaced
  • ❌ Brittle - may break if campus-api-python internals change

Proposed Solution

Add a configurable class attribute to allow dependency injection of the JsonClient class:

class Campus:
    """Unified Campus client interface."""
    
    # Configurable JsonClient class
    json_client_class: type[JsonClient] = CampusRequest
    
    @property
    def auth(self) -> AuthRoot:
        if not hasattr(self, "_auth"):
            # Use json_client_class instead of hardcoded CampusRequest
            self._auth = AuthRoot(
                json_client=self.json_client_class(
                    base_url=base_url,
                    timeout=self.timeout,
                )
            )
        return self._auth
    
    @property
    def api(self) -> ApiRoot:
        if not hasattr(self, "_api"):
            self._api = ApiRoot(
                json_client=self.json_client_class(
                    base_url=base_url,
                    timeout=self.timeout,
                )
            )
        return self._api
Usage in Tests
import campus_python
from tests.flask_test import TestCampusRequest

def setup():
    # Configure campus_python to use test client
    campus_python.Campus.json_client_class = TestCampusRequest
    
    # Now all Campus instances use TestCampusRequest
    campus = campus_python.Campus(timeout=60)
    campus.auth.root.authenticate(...)  # Uses Flask test clients!

Benefits

  1. Clean dependency injection - No monkey-patching required
  2. Explicit configuration - Clear what JsonClient is being used
  3. Backward compatible - Defaults to CampusRequest
  4. Test-friendly - Easy to inject test doubles
  5. Flexible - Allows custom JsonClient implementations for:
    • Testing (Flask test clients)
    • Mocking (for unit tests)
    • Custom HTTP backends (async, retry logic, etc.)

Implementation Details

Changes Required

File: campus_python/__init__.py

  1. Add class attribute:

    class Campus:
        json_client_class: type[JsonClient] = CampusRequest
    
  2. Replace hardcoded CampusRequest(...) with self.json_client_class(...):

    • In auth property (line ~81)
    • In api property (line ~107)
Example Custom JsonClient
from campus_python.json_client.interface import JsonClient, JsonResponse

class CustomJsonClient(JsonClient):
    """Custom JsonClient with special behavior."""
    
    def __init__(self, base_url: str | None = None, **kwargs):
        self.base_url = base_url or ""
        # ... custom initialization ...
    
    def get(self, path: str, query: dict | None = None) -> JsonResponse:
        # ... custom implementation ...
        pass
    
    # ... implement other methods ...

# Use it
campus_python.Campus.json_client_class = CustomJsonClient

Backward Compatibility

Fully backward compatible - Default value is CampusRequest, so existing code continues to work without changes.

Related

Alternatives Considered

  1. Constructor parameter (Campus(json_client_class=...))

    • ❌ Doesn't work for services that instantiate Campus() internally (campus.auth, campus.api)
  2. Global function (set_json_client_class())

    • ❌ More verbose than class attribute
    • ❌ Requires additional function to maintain
  3. Keep monkey-patching

    • ❌ Fragile and confusing

Implementation Checklist

  • Add json_client_class class attribute to Campus
  • Update auth property to use self.json_client_class
  • Update api property to use self.json_client_class
  • Add docstring explaining the configuration option
  • Add example to README or documentation
  • Release as minor version bump (e.g., v2.1.0)
主要语言
Python
星标
0
派生
0
PR 合并指标
30 天内没有已合并 PR

贡献指南

这个仓库没有索引到贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

nyjc-computing/campus-api-python 的其他 Issue

查看 nyjc-computing/campus-api-python 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。