Coverage for src/usaspending/queries/single_resource_base.py: 90%

29 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-09-03 17:15 -0700

1from ..exceptions import ValidationError 

2from ..client import USASpending 

3from typing import Any 

4from abc import ABC, abstractmethod 

5from ..logging_config import USASpendingLogger 

6 

7logger = USASpendingLogger.get_logger(__name__) 

8 

9 

10class SingleResourceBase(ABC): 

11 """ 

12 Base class for retrieving single resources like awards or recipients, 

13 anything with a simple GET request, generally. 

14 """ 

15 

16 def __init__(self, client: USASpending): 

17 self._client = client 

18 

19 @property 

20 @abstractmethod 

21 def _endpoint(self) -> str: 

22 """Base endpoint for single resource retrieval.""" 

23 pass 

24 

25 @abstractmethod 

26 def find_by_id(self, resource_id: str) -> Any: 

27 """Filter by unique resource identifier.""" 

28 pass 

29 

30 def _get_resource(self, resource_id: str) -> dict: 

31 """Retrieve a single resource by ID.""" 

32 if not resource_id or not isinstance(resource_id, str) or not resource_id.strip(): 

33 raise ValidationError("A non-empty resource_id string is required") 

34 

35 # Clean recipient ID 

36 cleaned_resource_id = self._clean_resource_id(resource_id) 

37 

38 if not cleaned_resource_id: 

39 raise ValidationError( 

40 "No resource id found after cleaning. Original: %s. Cleaned: %s", 

41 resource_id, 

42 cleaned_resource_id, 

43 ) 

44 

45 # Construct valid endpoint 

46 endpoint = self._construct_endpoint(cleaned_resource_id) 

47 

48 # Make API request 

49 response = self._client._make_request("GET", endpoint) 

50 

51 return response 

52 

53 def _construct_endpoint(self, resource_id: str) -> str: 

54 """Construct the full endpoint URL for a specific resource ID.""" 

55 return f"{self._endpoint}{resource_id}/" 

56 

57 def _clean_resource_id(self, resource_id: str) -> str: 

58 """Very basic resource ID cleaning. 

59 

60 More complex logic is implemented in specific resource classes. 

61 

62 Args: 

63 resource_id: The raw resource ID string 

64 

65 Returns: 

66 Cleaned resource ID string 

67 """ 

68 return str(resource_id).strip()