-
Notifications
You must be signed in to change notification settings - Fork 1
Description
Refactor async metadata.py - Reduce Code Duplication
Overview
Analysis of ofsc/async_client/metadata.py (2,340 lines) identified 6 repetitive patterns that can be refactored using helper methods and generics.
Estimated Reduction: ~533 lines (~23% of file)
Patterns Identified
| Pattern | Occurrences | Current Lines | After Refactor | Lines Saved |
|---|---|---|---|---|
| GET List with Pagination | 16 | ~224 | ~36 | ~188 |
| GET Single Item by Label | 17 | ~238 | ~37 | ~201 |
| GET All (no pagination) | 4 | ~48 | ~19 | ~29 |
| PUT/Create-Replace | 4 | ~72 | ~37 | ~35 |
| DELETE | 2 | ~24 | ~14 | ~10 |
| Links Removal | 40+ | ~80 | ~10 | ~70 |
| Total | - | ~686 | ~153 | ~533 |
Pattern 1: GET List with Pagination (16 methods)
Affected Methods:
- get_activity_type_groups, get_activity_types, get_forms
- get_inventory_types, get_languages, get_link_templates
- get_map_layers, get_non_working_reasons, get_properties
- get_routing_profiles, get_shifts, get_time_slots
- get_work_skills, get_work_skill_groups, get_work_zones
- get_capacity_categories
Refactor: Create _get_paginated_list() helper with TypeVar generic
Pattern 2: GET Single Item by Label (17 methods)
Affected Methods:
- get_activity_type_group, get_activity_type, get_application
- get_capacity_area, get_capacity_category, get_form
- get_inventory_type, get_language, get_link_template
- get_map_layer, get_non_working_reason, get_organization
- get_property, get_shift, get_time_slot
- get_work_skill, get_workzone
Refactor: Create _get_single_item() helper with TypeVar generic
Pattern 3: GET All (4 methods)
Affected Methods:
- get_applications, get_organizations
- get_resource_types, get_capacity_areas
Refactor: Create _get_all_items() helper
Pattern 4: PUT/Create-Replace (4 methods)
Affected Methods:
- create_or_replace_property, create_workzone
- replace_workzone, create_or_update_enumeration_values
Refactor: Create _put_item() helper
Pattern 5: DELETE (2 methods)
Affected Methods:
- delete_workzone, delete_property
Refactor: Create _delete_item() helper
Pattern 6: Links Removal (40+ methods)
Inconsistent patterns for removing 'links' from API responses
Refactor: Create _clean_response() helper used by all helpers
Bug Found: Inconsistent quote_plus Usage
Some methods encode labels with quote_plus(), others don't:
| Method | Uses quote_plus |
|---|---|
| get_activity_type | ✅ Yes |
| get_property | ❌ No |
| get_workzone | ❌ No |
| get_form | ✅ Yes |
Risk: Labels with special characters (spaces, +, %) may fail
Fix: Standardize by encoding all labels in _get_single_item() helper
Proposed Helper Methods
from typing import TypeVar, Type
T = TypeVar("T")
def _clean_response(self, data: dict) -> dict:
"""Remove API-specific keys not in models."""
data.pop("links", None)
return data
async def _get_paginated_list(
self,
endpoint: str,
response_model: Type[T],
error_context: str,
offset: int = 0,
limit: int = 100,
extra_params: dict | None = None
) -> T:
"""Generic GET list with pagination."""
...
async def _get_single_item(
self,
endpoint_template: str,
label: str,
response_model: Type[T],
error_context: str
) -> T:
"""Generic GET single item by label."""
...
async def _get_all_items(
self,
endpoint: str,
response_model: Type[T],
error_context: str
) -> T:
"""Generic GET all items without pagination."""
...
async def _put_item(
self,
endpoint: str,
data: BaseModel,
response_model: Type[T],
error_context: str
) -> T:
"""Generic PUT item."""
...
async def _delete_item(
self,
endpoint_template: str,
label: str,
error_context: str
) -> None:
"""Generic DELETE item by label."""
...Example: Before and After
Before (14 lines):
async def get_activity_types(self, offset: int = 0, limit: int = 100) -> ActivityTypeListResponse:
"""Get activity types with pagination."""
url = urljoin(self.baseUrl, "/rest/ofscMetadata/v1/activityTypes")
params = {"offset": offset, "limit": limit}
try:
response = await self._client.get(url, headers=self.headers, params=params)
response.raise_for_status()
data = response.json()
if "links" in data and not hasattr(ActivityTypeListResponse, "links"):
del data["links"]
return ActivityTypeListResponse.model_validate(data)
except httpx.HTTPStatusError as e:
self._handle_http_error(e, "Failed to get activity types")
raise
except httpx.TransportError as e:
raise OFSCNetworkError(f"Network error: {str(e)}") from eAfter (7 lines):
async def get_activity_types(self, offset: int = 0, limit: int = 100) -> ActivityTypeListResponse:
"""Get activity types with pagination."""
return await self._get_paginated_list(
"/rest/ofscMetadata/v1/activityTypes",
ActivityTypeListResponse,
"Failed to get activity types",
offset, limit
)Implementation Plan
Phase 1: Add Helper Methods
- Add
_clean_response()helper - Add
_get_paginated_list()helper - Add
_get_single_item()helper - Add
_get_all_items()helper - Add
_put_item()helper - Add
_delete_item()helper
Phase 2: Refactor Methods
- Refactor 16 paginated list methods
- Refactor 17 single item methods
- Refactor 4 get-all methods
- Refactor 4 PUT methods
- Refactor 2 DELETE methods
Phase 3: Bug Fixes
- Fix
quote_plusin get_property, get_workzone - Standardize links removal pattern
Testing Strategy
# Run tests before each phase
uv run pytest tests/async/ -v
# Ensure 100% test pass rate after each changeBenefits
- Reduced LOC: ~533 lines removed (~23%)
- Better Maintainability: Single source of truth for API patterns
- Consistency: All methods follow same error handling pattern
- Bug Fixes: Standardizes
quote_plususage - Type Safety: TypeVar generics enable IDE type inference
- Easier Testing: Helpers can be tested independently
References
- Analysis Plan: See
/Users/borja.toron/.claude/plans/magical-bouncing-curry.md - Target File:
ofsc/async_client/metadata.py