This document describes the standardization work done to ensure consistency across all InsightVM-Python API modules, particularly focusing on the integration of recently merged Scans and Reports API modules.
When reviewing recently merged changes (Scans API and Reports API modules), we identified critical inconsistencies in how different API modules were calling the BaseAPI:
Old Pattern (Assets, Sites, Asset Groups):
# Uses helper methods and manually parses JSON
response = self.get('assets', params=params)
return response.json() # Manual .json() callNew Pattern (Scans, Reports):
# Uses _request() directly, expects JSON back
return self._request('GET', 'scans', params=params)
# Expected to return Dict[str, Any] but was returning Response objectBaseAPI._request() Behavior:
- Originally returned
requests.Responseobjects - New modules expected it to return parsed JSON dictionaries
- This mismatch meant the new modules were broken
Updated _request() to automatically parse JSON by default:
def _request(
self,
method: str,
endpoint: str,
params: Optional[Dict[str, Any]] = None,
json: Optional[Dict[str, Any]] = None,
headers: Optional[Dict[str, str]] = None,
return_raw: bool = False, # NEW parameter
**kwargs
) -> Any:
"""
Make an API request with automatic JSON parsing.
By default, returns parsed JSON dictionaries.
For binary content (downloads), use return_raw=True.
"""
# ... request logic ...
if return_raw:
return response # Raw Response object
return response.json() # Parsed JSON (default)Key Changes:
- Default behavior: Returns parsed JSON dictionaries
- New parameter
return_raw: When True, returns raw Response object - Use case for
return_raw=True: Binary downloads (PDF, files, etc.)
Updated helper methods to maintain compatibility with existing code:
def get(self, endpoint: str, params: Optional[Dict[str, Any]] = None, **kwargs) -> requests.Response:
"""Returns raw Response for backward compatibility."""
return self._request('GET', endpoint, params=params, return_raw=True, **kwargs)All helper methods (get, post, put, delete) now pass return_raw=True to maintain backward compatibility with existing modules.
For JSON responses:
def list(self, page: int = 0, size: int = 500) -> Dict[str, Any]:
"""List resources with automatic JSON parsing."""
params = {'page': page, 'size': size}
return self._request('GET', 'scans', params=params)
# Returns parsed JSON dictionary directlyFor binary content (downloads):
def download(self, report_id: int, instance_id: str) -> bytes:
"""Download report content."""
response = self._request(
'GET',
f'reports/{report_id}/history/{instance_id}/output',
return_raw=True # Get raw Response for binary content
)
return response.contentdef list(self, page: int = 0, size: int = 500) -> Dict[str, Any]:
"""List resources using legacy pattern."""
params = {'page': page, 'size': size}
response = self.get('assets', params=params) # Returns Response
return response.json() # Manual JSON parsingThese modules use _request() directly and benefit from automatic JSON parsing:
-
ScansAPI (
src/rapid7/api/scans.py)- All methods use
_request()directly - Returns parsed JSON dictionaries
- Fully compatible with new standardization
- All methods use
-
ReportsAPI (
src/rapid7/api/reports.py)- Most methods use
_request()directly download()method correctly usesreturn_raw=Truefor binary content- Fully compatible with new standardization
- Most methods use
These modules use helper methods and manual .json() calls:
-
AssetAPI (
src/rapid7/api/assets.py)- Uses
self.get(),self.post(), etc. - Manually calls
.json()on responses - Works correctly due to backward compatibility
- Should be migrated to modern pattern eventually
- Uses
-
SiteAPI (
src/rapid7/api/sites.py)- Uses
self.get(),self.post(), etc. - Manually calls
.json()on responses - Works correctly due to backward compatibility
- Should be migrated to modern pattern eventually
- Uses
-
AssetGroupAPI (
src/rapid7/api/asset_groups.py)- Uses
self.get(),self.post(), etc. - Manually calls
.json()on responses - Works correctly due to backward compatibility
- Should be migrated to modern pattern eventually
- Uses
-
SonarQueryAPI (
src/rapid7/api/sonar_queries.py)- Status: Needs review
- Should be migrated to modern pattern eventually
When creating new API modules, use the modern pattern:
class NewAPI(BaseAPI):
"""New API module."""
def list(self, page: int = 0, size: int = 500) -> Dict[str, Any]:
"""List resources."""
params = {'page': page, 'size': size}
# Direct _request() usage - returns JSON automatically
return self._request('GET', 'resource', params=params)
def get(self, resource_id: int) -> Dict[str, Any]:
"""Get single resource."""
return self._request('GET', f'resource/{resource_id}')
def download(self, resource_id: int) -> bytes:
"""Download binary content."""
response = self._request(
'GET',
f'resource/{resource_id}/download',
return_raw=True # For binary content
)
return response.contentLegacy modules will continue to work without changes. To migrate to the modern pattern:
Before:
def list(self, page: int = 0, size: int = 500) -> Dict[str, Any]:
params = {'page': page, 'size': size}
response = self.get('assets', params=params)
return response.json()After:
def list(self, page: int = 0, size: int = 500) -> Dict[str, Any]:
params = {'page': page, 'size': size}
return self._request('GET', 'assets', params=params)- Consistency: All new modules follow the same pattern
- Simplicity: No need for manual
.json()calls - Type Safety: Methods return
Dict[str, Any]as declared - Clarity: Intent is clearer with
return_raw=Truefor binary content - Backward Compatible: Existing code continues to work
- Future-Proof: Easy to add new features to
_request()
After migrating a module to the modern pattern:
- Verify JSON responses: Ensure methods return dictionaries, not Response objects
- Test binary downloads: Confirm
return_raw=Trueworks for file downloads - Check error handling: Verify HTTPError exceptions are raised correctly
- Validate pagination: Test automatic pagination with
get_all()methods
These migrations are optional since backward compatibility is maintained:
- Migrate AssetAPI to modern pattern
- Migrate SiteAPI to modern pattern
- Migrate AssetGroupAPI to modern pattern
- Migrate SonarQueryAPI to modern pattern
- No breaking changes: Migrations are optional improvements
- Testing required: Each migration should be tested
- Documentation: Update module docstrings to reflect modern pattern
- Low priority: Focus on new features first
The standardization ensures:
- New modules (Scans, Reports) work correctly with automatic JSON parsing
- Old modules (Assets, Sites, etc.) continue to work via backward compatibility
- Consistent patterns for all future API module development
- Clear migration path for eventually standardizing all modules
All modules now work correctly, and we have a clear, documented pattern for future development.