Implement async/await support with AsyncWikiJSClient

Phase 2, Task 2.1, Steps 1-3 Complete: Async Client Architecture

This commit introduces comprehensive async/await support for the Wiki.js
Python SDK, providing high-performance concurrent operations using aiohttp.

Key Features:
------------

1. **AsyncWikiJSClient** (wikijs/aio/client.py)
   - Full async/await support with aiohttp
   - Connection pooling (100 connections, 30 per host)
   - Async context manager support (async with)
   - Same interface as sync client for easy migration
   - Proper resource cleanup and session management
   - DNS caching for improved performance

2. **Async Endpoints** (wikijs/aio/endpoints/)
   - AsyncBaseEndpoint - Base class for all async endpoints
   - AsyncPagesEndpoint - Complete async Pages API
     * list() - List pages with filtering
     * get() - Get page by ID
     * get_by_path() - Get page by path
     * create() - Create new page
     * update() - Update existing page
     * delete() - Delete page
     * search() - Search pages
     * get_by_tags() - Filter by tags

3. **Architecture**
   - Mirrors sync client structure for consistency
   - Reuses existing models, exceptions, and utilities
   - Optional dependency (aiohttp) via extras_require
   - Zero breaking changes to sync API

Performance Benefits:
--------------------
- Designed for >3x throughput vs sync client
- Efficient connection pooling and reuse
- Concurrent request handling
- Reduced latency with TCP keepalive

Usage Example:
--------------
```python
from wikijs.aio import AsyncWikiJSClient

async with AsyncWikiJSClient(url, auth='key') as client:
    # Concurrent operations
    pages = await client.pages.list()
    page = await client.pages.get(123)

    # Create/Update/Delete
    new_page = await client.pages.create(page_data)
    updated = await client.pages.update(123, updates)
    await client.pages.delete(123)
```

Installation:
-------------
```bash
pip install wikijs-python-sdk[async]
```

Quality Metrics:
----------------
-  All imports successful
-  Black formatting applied
-  Flake8 passing (complexity warnings expected)
-  MyPy type checking (minor issues in base models)
-  Zero breaking changes to sync API

Next Steps:
-----------
- Comprehensive async unit tests
- Integration tests with real Wiki.js instance
- Performance benchmarks (async vs sync)
- Documentation and usage examples

This lays the foundation for high-performance async operations
in the Wiki.js Python SDK.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude
2025-10-22 18:07:29 +00:00
parent 9775d02699
commit 95c86b6600
6 changed files with 1237 additions and 2 deletions

View File

@@ -0,0 +1,140 @@
"""Base async endpoint class for wikijs-python-sdk."""
from typing import TYPE_CHECKING, Any, Dict, Optional
if TYPE_CHECKING:
from ..client import AsyncWikiJSClient
class AsyncBaseEndpoint:
"""Base class for all async API endpoints.
This class provides common functionality for making async API requests
and handling responses across all endpoint implementations.
Args:
client: The async WikiJS client instance
"""
def __init__(self, client: "AsyncWikiJSClient"):
"""Initialize endpoint with client reference.
Args:
client: Async WikiJS client instance
"""
self._client = client
async def _request(
self,
method: str,
endpoint: str,
params: Optional[Dict[str, Any]] = None,
json_data: Optional[Dict[str, Any]] = None,
**kwargs: Any,
) -> Any:
"""Make async HTTP request through the client.
Args:
method: HTTP method (GET, POST, PUT, DELETE)
endpoint: API endpoint path
params: Query parameters
json_data: JSON data for request body
**kwargs: Additional request parameters
Returns:
Parsed response data
"""
return await self._client._request(
method=method,
endpoint=endpoint,
params=params,
json_data=json_data,
**kwargs,
)
async def _get(
self, endpoint: str, params: Optional[Dict[str, Any]] = None, **kwargs: Any
) -> Any:
"""Make async GET request.
Args:
endpoint: API endpoint path
params: Query parameters
**kwargs: Additional request parameters
Returns:
Parsed response data
"""
return await self._request("GET", endpoint, params=params, **kwargs)
async def _post(
self,
endpoint: str,
json_data: Optional[Dict[str, Any]] = None,
params: Optional[Dict[str, Any]] = None,
**kwargs: Any,
) -> Any:
"""Make async POST request.
Args:
endpoint: API endpoint path
json_data: JSON data for request body
params: Query parameters
**kwargs: Additional request parameters
Returns:
Parsed response data
"""
return await self._request(
"POST", endpoint, params=params, json_data=json_data, **kwargs
)
async def _put(
self,
endpoint: str,
json_data: Optional[Dict[str, Any]] = None,
params: Optional[Dict[str, Any]] = None,
**kwargs: Any,
) -> Any:
"""Make async PUT request.
Args:
endpoint: API endpoint path
json_data: JSON data for request body
params: Query parameters
**kwargs: Additional request parameters
Returns:
Parsed response data
"""
return await self._request(
"PUT", endpoint, params=params, json_data=json_data, **kwargs
)
async def _delete(
self, endpoint: str, params: Optional[Dict[str, Any]] = None, **kwargs: Any
) -> Any:
"""Make async DELETE request.
Args:
endpoint: API endpoint path
params: Query parameters
**kwargs: Additional request parameters
Returns:
Parsed response data
"""
return await self._request("DELETE", endpoint, params=params, **kwargs)
def _build_endpoint(self, *parts: str) -> str:
"""Build endpoint path from parts.
Args:
*parts: Path components
Returns:
Formatted endpoint path
"""
# Remove empty parts and join with /
clean_parts = [str(part).strip("/") for part in parts if part]
return "/" + "/".join(clean_parts)