From bb0ec36d327277d404987d08df5857cd01e72ad1 Mon Sep 17 00:00:00 2001 From: iancart Date: Wed, 16 Apr 2025 13:38:56 -0400 Subject: [PATCH 1/4] Fixed an issue where the getOrganizationCameraDetectionsHistoryByBoundaryByInterval function did not include the 'ranges' array query parameter --- meraki/api/camera.py | 128 +++++++++---------------------------------- 1 file changed, 25 insertions(+), 103 deletions(-) diff --git a/meraki/api/camera.py b/meraki/api/camera.py index f05a3d4..5122dc3 100644 --- a/meraki/api/camera.py +++ b/meraki/api/camera.py @@ -5,8 +5,6 @@ class Camera(object): def __init__(self, session): super(Camera, self).__init__() self._session = session - - def getDeviceCameraAnalyticsLive(self, serial: str): """ @@ -24,8 +22,6 @@ def getDeviceCameraAnalyticsLive(self, serial: str): resource = f'/devices/{serial}/camera/analytics/live' return self._session.get(metadata, resource) - - def getDeviceCameraAnalyticsOverview(self, serial: str, **kwargs): """ @@ -43,7 +39,8 @@ def getDeviceCameraAnalyticsOverview(self, serial: str, **kwargs): if 'objectType' in kwargs: options = ['person', 'vehicle'] - assert kwargs['objectType'] in options, f'''"objectType" cannot be "{kwargs['objectType']}", & must be set to one of: {options}''' + assert kwargs[ + 'objectType'] in options, f'''"objectType" cannot be "{kwargs['objectType']}", & must be set to one of: {options}''' metadata = { 'tags': ['camera', 'monitor', 'analytics', 'overview'], @@ -56,8 +53,6 @@ def getDeviceCameraAnalyticsOverview(self, serial: str, **kwargs): params = {k.strip(): v for k, v in kwargs.items() if k.strip() in query_params} return self._session.get(metadata, resource, params) - - def getDeviceCameraAnalyticsRecent(self, serial: str, **kwargs): """ @@ -72,7 +67,8 @@ def getDeviceCameraAnalyticsRecent(self, serial: str, **kwargs): if 'objectType' in kwargs: options = ['person', 'vehicle'] - assert kwargs['objectType'] in options, f'''"objectType" cannot be "{kwargs['objectType']}", & must be set to one of: {options}''' + assert kwargs[ + 'objectType'] in options, f'''"objectType" cannot be "{kwargs['objectType']}", & must be set to one of: {options}''' metadata = { 'tags': ['camera', 'monitor', 'analytics', 'recent'], @@ -85,8 +81,6 @@ def getDeviceCameraAnalyticsRecent(self, serial: str, **kwargs): params = {k.strip(): v for k, v in kwargs.items() if k.strip() in query_params} return self._session.get(metadata, resource, params) - - def getDeviceCameraAnalyticsZones(self, serial: str): """ @@ -104,8 +98,6 @@ def getDeviceCameraAnalyticsZones(self, serial: str): resource = f'/devices/{serial}/camera/analytics/zones' return self._session.get(metadata, resource) - - def getDeviceCameraAnalyticsZoneHistory(self, serial: str, zoneId: str, **kwargs): """ @@ -125,7 +117,8 @@ def getDeviceCameraAnalyticsZoneHistory(self, serial: str, zoneId: str, **kwargs if 'objectType' in kwargs: options = ['person', 'vehicle'] - assert kwargs['objectType'] in options, f'''"objectType" cannot be "{kwargs['objectType']}", & must be set to one of: {options}''' + assert kwargs[ + 'objectType'] in options, f'''"objectType" cannot be "{kwargs['objectType']}", & must be set to one of: {options}''' metadata = { 'tags': ['camera', 'monitor', 'analytics', 'zones', 'history'], @@ -139,8 +132,6 @@ def getDeviceCameraAnalyticsZoneHistory(self, serial: str, zoneId: str, **kwargs params = {k.strip(): v for k, v in kwargs.items() if k.strip() in query_params} return self._session.get(metadata, resource, params) - - def getDeviceCameraCustomAnalytics(self, serial: str): """ @@ -158,8 +149,6 @@ def getDeviceCameraCustomAnalytics(self, serial: str): resource = f'/devices/{serial}/camera/customAnalytics' return self._session.get(metadata, resource) - - def updateDeviceCameraCustomAnalytics(self, serial: str, **kwargs): """ @@ -185,8 +174,6 @@ def updateDeviceCameraCustomAnalytics(self, serial: str, **kwargs): payload = {k.strip(): v for k, v in kwargs.items() if k.strip() in body_params} return self._session.put(metadata, resource, payload) - - def generateDeviceCameraSnapshot(self, serial: str, **kwargs): """ @@ -211,8 +198,6 @@ def generateDeviceCameraSnapshot(self, serial: str, **kwargs): payload = {k.strip(): v for k, v in kwargs.items() if k.strip() in body_params} return self._session.post(metadata, resource, payload) - - def getDeviceCameraQualityAndRetention(self, serial: str): """ @@ -230,8 +215,6 @@ def getDeviceCameraQualityAndRetention(self, serial: str): resource = f'/devices/{serial}/camera/qualityAndRetention' return self._session.get(metadata, resource) - - def updateDeviceCameraQualityAndRetention(self, serial: str, **kwargs): """ @@ -252,13 +235,16 @@ def updateDeviceCameraQualityAndRetention(self, serial: str, **kwargs): if 'quality' in kwargs: options = ['Enhanced', 'High', 'Standard', 'Ultra'] - assert kwargs['quality'] in options, f'''"quality" cannot be "{kwargs['quality']}", & must be set to one of: {options}''' + assert kwargs[ + 'quality'] in options, f'''"quality" cannot be "{kwargs['quality']}", & must be set to one of: {options}''' if 'resolution' in kwargs: options = ['1080x1080', '1280x720', '1920x1080', '2112x2112', '2688x1512', '2880x2880', '3840x2160'] - assert kwargs['resolution'] in options, f'''"resolution" cannot be "{kwargs['resolution']}", & must be set to one of: {options}''' + assert kwargs[ + 'resolution'] in options, f'''"resolution" cannot be "{kwargs['resolution']}", & must be set to one of: {options}''' if 'motionDetectorVersion' in kwargs: options = [1, 2] - assert kwargs['motionDetectorVersion'] in options, f'''"motionDetectorVersion" cannot be "{kwargs['motionDetectorVersion']}", & must be set to one of: {options}''' + assert kwargs[ + 'motionDetectorVersion'] in options, f'''"motionDetectorVersion" cannot be "{kwargs['motionDetectorVersion']}", & must be set to one of: {options}''' metadata = { 'tags': ['camera', 'configure', 'qualityAndRetention'], @@ -267,12 +253,11 @@ def updateDeviceCameraQualityAndRetention(self, serial: str, **kwargs): serial = urllib.parse.quote(str(serial), safe='') resource = f'/devices/{serial}/camera/qualityAndRetention' - body_params = ['profileId', 'motionBasedRetentionEnabled', 'audioRecordingEnabled', 'restrictedBandwidthModeEnabled', 'quality', 'resolution', 'motionDetectorVersion', ] + body_params = ['profileId', 'motionBasedRetentionEnabled', 'audioRecordingEnabled', + 'restrictedBandwidthModeEnabled', 'quality', 'resolution', 'motionDetectorVersion', ] payload = {k.strip(): v for k, v in kwargs.items() if k.strip() in body_params} return self._session.put(metadata, resource, payload) - - def getDeviceCameraSense(self, serial: str): """ @@ -290,8 +275,6 @@ def getDeviceCameraSense(self, serial: str): resource = f'/devices/{serial}/camera/sense' return self._session.get(metadata, resource) - - def updateDeviceCameraSense(self, serial: str, **kwargs): """ @@ -318,8 +301,6 @@ def updateDeviceCameraSense(self, serial: str, **kwargs): payload = {k.strip(): v for k, v in kwargs.items() if k.strip() in body_params} return self._session.put(metadata, resource, payload) - - def getDeviceCameraSenseObjectDetectionModels(self, serial: str): """ @@ -337,8 +318,6 @@ def getDeviceCameraSenseObjectDetectionModels(self, serial: str): resource = f'/devices/{serial}/camera/sense/objectDetectionModels' return self._session.get(metadata, resource) - - def getDeviceCameraVideoSettings(self, serial: str): """ @@ -356,8 +335,6 @@ def getDeviceCameraVideoSettings(self, serial: str): resource = f'/devices/{serial}/camera/video/settings' return self._session.get(metadata, resource) - - def updateDeviceCameraVideoSettings(self, serial: str, **kwargs): """ @@ -381,8 +358,6 @@ def updateDeviceCameraVideoSettings(self, serial: str, **kwargs): payload = {k.strip(): v for k, v in kwargs.items() if k.strip() in body_params} return self._session.put(metadata, resource, payload) - - def getDeviceCameraVideoLink(self, serial: str, **kwargs): """ @@ -406,8 +381,6 @@ def getDeviceCameraVideoLink(self, serial: str, **kwargs): params = {k.strip(): v for k, v in kwargs.items() if k.strip() in query_params} return self._session.get(metadata, resource, params) - - def getDeviceCameraWirelessProfiles(self, serial: str): """ @@ -425,8 +398,6 @@ def getDeviceCameraWirelessProfiles(self, serial: str): resource = f'/devices/{serial}/camera/wirelessProfiles' return self._session.get(metadata, resource) - - def updateDeviceCameraWirelessProfiles(self, serial: str, ids: dict): """ @@ -450,8 +421,6 @@ def updateDeviceCameraWirelessProfiles(self, serial: str, ids: dict): payload = {k.strip(): v for k, v in kwargs.items() if k.strip() in body_params} return self._session.put(metadata, resource, payload) - - def getNetworkCameraQualityRetentionProfiles(self, networkId: str): """ @@ -469,8 +438,6 @@ def getNetworkCameraQualityRetentionProfiles(self, networkId: str): resource = f'/networks/{networkId}/camera/qualityRetentionProfiles' return self._session.get(metadata, resource) - - def createNetworkCameraQualityRetentionProfile(self, networkId: str, name: str, **kwargs): """ @@ -499,12 +466,12 @@ def createNetworkCameraQualityRetentionProfile(self, networkId: str, name: str, networkId = urllib.parse.quote(str(networkId), safe='') resource = f'/networks/{networkId}/camera/qualityRetentionProfiles' - body_params = ['name', 'motionBasedRetentionEnabled', 'restrictedBandwidthModeEnabled', 'audioRecordingEnabled', 'cloudArchiveEnabled', 'motionDetectorVersion', 'smartRetention', 'scheduleId', 'maxRetentionDays', 'videoSettings', ] + body_params = ['name', 'motionBasedRetentionEnabled', 'restrictedBandwidthModeEnabled', 'audioRecordingEnabled', + 'cloudArchiveEnabled', 'motionDetectorVersion', 'smartRetention', 'scheduleId', + 'maxRetentionDays', 'videoSettings', ] payload = {k.strip(): v for k, v in kwargs.items() if k.strip() in body_params} return self._session.post(metadata, resource, payload) - - def getNetworkCameraQualityRetentionProfile(self, networkId: str, qualityRetentionProfileId: str): """ @@ -524,8 +491,6 @@ def getNetworkCameraQualityRetentionProfile(self, networkId: str, qualityRetenti resource = f'/networks/{networkId}/camera/qualityRetentionProfiles/{qualityRetentionProfileId}' return self._session.get(metadata, resource) - - def updateNetworkCameraQualityRetentionProfile(self, networkId: str, qualityRetentionProfileId: str, **kwargs): """ @@ -556,12 +521,12 @@ def updateNetworkCameraQualityRetentionProfile(self, networkId: str, qualityRete qualityRetentionProfileId = urllib.parse.quote(str(qualityRetentionProfileId), safe='') resource = f'/networks/{networkId}/camera/qualityRetentionProfiles/{qualityRetentionProfileId}' - body_params = ['name', 'motionBasedRetentionEnabled', 'restrictedBandwidthModeEnabled', 'audioRecordingEnabled', 'cloudArchiveEnabled', 'motionDetectorVersion', 'smartRetention', 'scheduleId', 'maxRetentionDays', 'videoSettings', ] + body_params = ['name', 'motionBasedRetentionEnabled', 'restrictedBandwidthModeEnabled', 'audioRecordingEnabled', + 'cloudArchiveEnabled', 'motionDetectorVersion', 'smartRetention', 'scheduleId', + 'maxRetentionDays', 'videoSettings', ] payload = {k.strip(): v for k, v in kwargs.items() if k.strip() in body_params} return self._session.put(metadata, resource, payload) - - def deleteNetworkCameraQualityRetentionProfile(self, networkId: str, qualityRetentionProfileId: str): """ @@ -581,8 +546,6 @@ def deleteNetworkCameraQualityRetentionProfile(self, networkId: str, qualityRete resource = f'/networks/{networkId}/camera/qualityRetentionProfiles/{qualityRetentionProfileId}' return self._session.delete(metadata, resource) - - def getNetworkCameraSchedules(self, networkId: str): """ @@ -600,8 +563,6 @@ def getNetworkCameraSchedules(self, networkId: str): resource = f'/networks/{networkId}/camera/schedules' return self._session.get(metadata, resource) - - def createNetworkCameraWirelessProfile(self, networkId: str, name: str, ssid: dict, **kwargs): """ @@ -627,8 +588,6 @@ def createNetworkCameraWirelessProfile(self, networkId: str, name: str, ssid: di payload = {k.strip(): v for k, v in kwargs.items() if k.strip() in body_params} return self._session.post(metadata, resource, payload) - - def getNetworkCameraWirelessProfiles(self, networkId: str): """ @@ -646,8 +605,6 @@ def getNetworkCameraWirelessProfiles(self, networkId: str): resource = f'/networks/{networkId}/camera/wirelessProfiles' return self._session.get(metadata, resource) - - def getNetworkCameraWirelessProfile(self, networkId: str, wirelessProfileId: str): """ @@ -667,8 +624,6 @@ def getNetworkCameraWirelessProfile(self, networkId: str, wirelessProfileId: str resource = f'/networks/{networkId}/camera/wirelessProfiles/{wirelessProfileId}' return self._session.get(metadata, resource) - - def updateNetworkCameraWirelessProfile(self, networkId: str, wirelessProfileId: str, **kwargs): """ @@ -696,8 +651,6 @@ def updateNetworkCameraWirelessProfile(self, networkId: str, wirelessProfileId: payload = {k.strip(): v for k, v in kwargs.items() if k.strip() in body_params} return self._session.put(metadata, resource, payload) - - def deleteNetworkCameraWirelessProfile(self, networkId: str, wirelessProfileId: str): """ @@ -717,8 +670,6 @@ def deleteNetworkCameraWirelessProfile(self, networkId: str, wirelessProfileId: resource = f'/networks/{networkId}/camera/wirelessProfiles/{wirelessProfileId}' return self._session.delete(metadata, resource) - - def getOrganizationCameraBoundariesAreasByDevice(self, organizationId: str, **kwargs): """ @@ -748,8 +699,6 @@ def getOrganizationCameraBoundariesAreasByDevice(self, organizationId: str, **kw params.pop(k.strip()) return self._session.get(metadata, resource, params) - - def getOrganizationCameraBoundariesLinesByDevice(self, organizationId: str, **kwargs): """ @@ -779,8 +728,6 @@ def getOrganizationCameraBoundariesLinesByDevice(self, organizationId: str, **kw params.pop(k.strip()) return self._session.get(metadata, resource, params) - - def getOrganizationCameraCustomAnalyticsArtifacts(self, organizationId: str): """ @@ -798,8 +745,6 @@ def getOrganizationCameraCustomAnalyticsArtifacts(self, organizationId: str): resource = f'/organizations/{organizationId}/camera/customAnalytics/artifacts' return self._session.get(metadata, resource) - - def createOrganizationCameraCustomAnalyticsArtifact(self, organizationId: str, **kwargs): """ @@ -823,8 +768,6 @@ def createOrganizationCameraCustomAnalyticsArtifact(self, organizationId: str, * payload = {k.strip(): v for k, v in kwargs.items() if k.strip() in body_params} return self._session.post(metadata, resource, payload) - - def getOrganizationCameraCustomAnalyticsArtifact(self, organizationId: str, artifactId: str): """ @@ -844,8 +787,6 @@ def getOrganizationCameraCustomAnalyticsArtifact(self, organizationId: str, arti resource = f'/organizations/{organizationId}/camera/customAnalytics/artifacts/{artifactId}' return self._session.get(metadata, resource) - - def deleteOrganizationCameraCustomAnalyticsArtifact(self, organizationId: str, artifactId: str): """ @@ -865,10 +806,9 @@ def deleteOrganizationCameraCustomAnalyticsArtifact(self, organizationId: str, a resource = f'/organizations/{organizationId}/camera/customAnalytics/artifacts/{artifactId}' return self._session.delete(metadata, resource) - - - def getOrganizationCameraDetectionsHistoryByBoundaryByInterval(self, organizationId: str, boundaryIds: list, total_pages=1, direction='next', **kwargs): + def getOrganizationCameraDetectionsHistoryByBoundaryByInterval(self, organizationId: str, boundaryIds: list, + total_pages=1, direction='next', **kwargs): """ **Returns analytics data for timespans** https://developer.cisco.com/meraki/api-v1/#!get-organization-camera-detections-history-by-boundary-by-interval @@ -891,18 +831,16 @@ def getOrganizationCameraDetectionsHistoryByBoundaryByInterval(self, organizatio organizationId = urllib.parse.quote(str(organizationId), safe='') resource = f'/organizations/{organizationId}/camera/detections/history/byBoundary/byInterval' - query_params = ['boundaryIds', 'duration', 'perPage', 'boundaryTypes', ] + query_params = ['boundaryIds', 'duration', 'perPage', 'boundaryTypes', 'ranges'] params = {k.strip(): v for k, v in kwargs.items() if k.strip() in query_params} - array_params = ['boundaryIds', 'boundaryTypes', ] + array_params = ['boundaryIds', 'boundaryTypes', 'ranges'] for k, v in kwargs.items(): if k.strip() in array_params: params[f'{k.strip()}[]'] = kwargs[f'{k}'] params.pop(k.strip()) return self._session.get_pages(metadata, resource, params, total_pages, direction) - - def getOrganizationCameraOnboardingStatuses(self, organizationId: str, **kwargs): """ @@ -933,8 +871,6 @@ def getOrganizationCameraOnboardingStatuses(self, organizationId: str, **kwargs) params.pop(k.strip()) return self._session.get(metadata, resource, params) - - def updateOrganizationCameraOnboardingStatuses(self, organizationId: str, **kwargs): """ @@ -959,8 +895,6 @@ def updateOrganizationCameraOnboardingStatuses(self, organizationId: str, **kwar payload = {k.strip(): v for k, v in kwargs.items() if k.strip() in body_params} return self._session.put(metadata, resource, payload) - - def getOrganizationCameraPermissions(self, organizationId: str): """ @@ -978,8 +912,6 @@ def getOrganizationCameraPermissions(self, organizationId: str): resource = f'/organizations/{organizationId}/camera/permissions' return self._session.get(metadata, resource) - - def getOrganizationCameraPermission(self, organizationId: str, permissionScopeId: str): """ @@ -999,8 +931,6 @@ def getOrganizationCameraPermission(self, organizationId: str, permissionScopeId resource = f'/organizations/{organizationId}/camera/permissions/{permissionScopeId}' return self._session.get(metadata, resource) - - def getOrganizationCameraRoles(self, organizationId: str): """ @@ -1018,8 +948,6 @@ def getOrganizationCameraRoles(self, organizationId: str): resource = f'/organizations/{organizationId}/camera/roles' return self._session.get(metadata, resource) - - def createOrganizationCameraRole(self, organizationId: str, name: str, **kwargs): """ @@ -1046,8 +974,6 @@ def createOrganizationCameraRole(self, organizationId: str, name: str, **kwargs) payload = {k.strip(): v for k, v in kwargs.items() if k.strip() in body_params} return self._session.post(metadata, resource, payload) - - def getOrganizationCameraRole(self, organizationId: str, roleId: str): """ @@ -1067,8 +993,6 @@ def getOrganizationCameraRole(self, organizationId: str, roleId: str): resource = f'/organizations/{organizationId}/camera/roles/{roleId}' return self._session.get(metadata, resource) - - def deleteOrganizationCameraRole(self, organizationId: str, roleId: str): """ @@ -1088,8 +1012,6 @@ def deleteOrganizationCameraRole(self, organizationId: str, roleId: str): resource = f'/organizations/{organizationId}/camera/roles/{roleId}' return self._session.delete(metadata, resource) - - def updateOrganizationCameraRole(self, organizationId: str, roleId: str, **kwargs): """ @@ -1118,4 +1040,4 @@ def updateOrganizationCameraRole(self, organizationId: str, roleId: str, **kwarg payload = {k.strip(): v for k, v in kwargs.items() if k.strip() in body_params} return self._session.put(metadata, resource, payload) - + From 98ade1a6526203fa29447706ab72949e117ef232 Mon Sep 17 00:00:00 2001 From: iancart Date: Wed, 16 Apr 2025 13:45:56 -0400 Subject: [PATCH 2/4] Fixed an issue where the getOrganizationCameraDetectionsHistoryByBoundaryByInterval function did not include the 'ranges' array query parameter --- meraki/api/camera.py | 123 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 100 insertions(+), 23 deletions(-) diff --git a/meraki/api/camera.py b/meraki/api/camera.py index 5122dc3..4841b73 100644 --- a/meraki/api/camera.py +++ b/meraki/api/camera.py @@ -6,6 +6,8 @@ def __init__(self, session): super(Camera, self).__init__() self._session = session + + def getDeviceCameraAnalyticsLive(self, serial: str): """ **Returns live state from camera analytics zones** @@ -23,6 +25,8 @@ def getDeviceCameraAnalyticsLive(self, serial: str): return self._session.get(metadata, resource) + + def getDeviceCameraAnalyticsOverview(self, serial: str, **kwargs): """ **Returns an overview of aggregate analytics data for a timespan** @@ -39,8 +43,7 @@ def getDeviceCameraAnalyticsOverview(self, serial: str, **kwargs): if 'objectType' in kwargs: options = ['person', 'vehicle'] - assert kwargs[ - 'objectType'] in options, f'''"objectType" cannot be "{kwargs['objectType']}", & must be set to one of: {options}''' + assert kwargs['objectType'] in options, f'''"objectType" cannot be "{kwargs['objectType']}", & must be set to one of: {options}''' metadata = { 'tags': ['camera', 'monitor', 'analytics', 'overview'], @@ -54,6 +57,8 @@ def getDeviceCameraAnalyticsOverview(self, serial: str, **kwargs): return self._session.get(metadata, resource, params) + + def getDeviceCameraAnalyticsRecent(self, serial: str, **kwargs): """ **Returns most recent record for analytics zones** @@ -67,8 +72,7 @@ def getDeviceCameraAnalyticsRecent(self, serial: str, **kwargs): if 'objectType' in kwargs: options = ['person', 'vehicle'] - assert kwargs[ - 'objectType'] in options, f'''"objectType" cannot be "{kwargs['objectType']}", & must be set to one of: {options}''' + assert kwargs['objectType'] in options, f'''"objectType" cannot be "{kwargs['objectType']}", & must be set to one of: {options}''' metadata = { 'tags': ['camera', 'monitor', 'analytics', 'recent'], @@ -82,6 +86,8 @@ def getDeviceCameraAnalyticsRecent(self, serial: str, **kwargs): return self._session.get(metadata, resource, params) + + def getDeviceCameraAnalyticsZones(self, serial: str): """ **Returns all configured analytic zones for this camera** @@ -99,6 +105,8 @@ def getDeviceCameraAnalyticsZones(self, serial: str): return self._session.get(metadata, resource) + + def getDeviceCameraAnalyticsZoneHistory(self, serial: str, zoneId: str, **kwargs): """ **Return historical records for analytic zones** @@ -117,8 +125,7 @@ def getDeviceCameraAnalyticsZoneHistory(self, serial: str, zoneId: str, **kwargs if 'objectType' in kwargs: options = ['person', 'vehicle'] - assert kwargs[ - 'objectType'] in options, f'''"objectType" cannot be "{kwargs['objectType']}", & must be set to one of: {options}''' + assert kwargs['objectType'] in options, f'''"objectType" cannot be "{kwargs['objectType']}", & must be set to one of: {options}''' metadata = { 'tags': ['camera', 'monitor', 'analytics', 'zones', 'history'], @@ -133,6 +140,8 @@ def getDeviceCameraAnalyticsZoneHistory(self, serial: str, zoneId: str, **kwargs return self._session.get(metadata, resource, params) + + def getDeviceCameraCustomAnalytics(self, serial: str): """ **Return custom analytics settings for a camera** @@ -150,6 +159,8 @@ def getDeviceCameraCustomAnalytics(self, serial: str): return self._session.get(metadata, resource) + + def updateDeviceCameraCustomAnalytics(self, serial: str, **kwargs): """ **Update custom analytics settings for a camera** @@ -175,6 +186,8 @@ def updateDeviceCameraCustomAnalytics(self, serial: str, **kwargs): return self._session.put(metadata, resource, payload) + + def generateDeviceCameraSnapshot(self, serial: str, **kwargs): """ **Generate a snapshot of what the camera sees at the specified time and return a link to that image.** @@ -199,6 +212,8 @@ def generateDeviceCameraSnapshot(self, serial: str, **kwargs): return self._session.post(metadata, resource, payload) + + def getDeviceCameraQualityAndRetention(self, serial: str): """ **Returns quality and retention settings for the given camera** @@ -216,6 +231,8 @@ def getDeviceCameraQualityAndRetention(self, serial: str): return self._session.get(metadata, resource) + + def updateDeviceCameraQualityAndRetention(self, serial: str, **kwargs): """ **Update quality and retention settings for the given camera** @@ -235,16 +252,13 @@ def updateDeviceCameraQualityAndRetention(self, serial: str, **kwargs): if 'quality' in kwargs: options = ['Enhanced', 'High', 'Standard', 'Ultra'] - assert kwargs[ - 'quality'] in options, f'''"quality" cannot be "{kwargs['quality']}", & must be set to one of: {options}''' + assert kwargs['quality'] in options, f'''"quality" cannot be "{kwargs['quality']}", & must be set to one of: {options}''' if 'resolution' in kwargs: options = ['1080x1080', '1280x720', '1920x1080', '2112x2112', '2688x1512', '2880x2880', '3840x2160'] - assert kwargs[ - 'resolution'] in options, f'''"resolution" cannot be "{kwargs['resolution']}", & must be set to one of: {options}''' + assert kwargs['resolution'] in options, f'''"resolution" cannot be "{kwargs['resolution']}", & must be set to one of: {options}''' if 'motionDetectorVersion' in kwargs: options = [1, 2] - assert kwargs[ - 'motionDetectorVersion'] in options, f'''"motionDetectorVersion" cannot be "{kwargs['motionDetectorVersion']}", & must be set to one of: {options}''' + assert kwargs['motionDetectorVersion'] in options, f'''"motionDetectorVersion" cannot be "{kwargs['motionDetectorVersion']}", & must be set to one of: {options}''' metadata = { 'tags': ['camera', 'configure', 'qualityAndRetention'], @@ -253,12 +267,13 @@ def updateDeviceCameraQualityAndRetention(self, serial: str, **kwargs): serial = urllib.parse.quote(str(serial), safe='') resource = f'/devices/{serial}/camera/qualityAndRetention' - body_params = ['profileId', 'motionBasedRetentionEnabled', 'audioRecordingEnabled', - 'restrictedBandwidthModeEnabled', 'quality', 'resolution', 'motionDetectorVersion', ] + body_params = ['profileId', 'motionBasedRetentionEnabled', 'audioRecordingEnabled', 'restrictedBandwidthModeEnabled', 'quality', 'resolution', 'motionDetectorVersion', ] payload = {k.strip(): v for k, v in kwargs.items() if k.strip() in body_params} return self._session.put(metadata, resource, payload) + + def getDeviceCameraSense(self, serial: str): """ **Returns sense settings for a given camera** @@ -276,6 +291,8 @@ def getDeviceCameraSense(self, serial: str): return self._session.get(metadata, resource) + + def updateDeviceCameraSense(self, serial: str, **kwargs): """ **Update sense settings for the given camera** @@ -302,6 +319,8 @@ def updateDeviceCameraSense(self, serial: str, **kwargs): return self._session.put(metadata, resource, payload) + + def getDeviceCameraSenseObjectDetectionModels(self, serial: str): """ **Returns the MV Sense object detection model list for the given camera** @@ -319,6 +338,8 @@ def getDeviceCameraSenseObjectDetectionModels(self, serial: str): return self._session.get(metadata, resource) + + def getDeviceCameraVideoSettings(self, serial: str): """ **Returns video settings for the given camera** @@ -336,6 +357,8 @@ def getDeviceCameraVideoSettings(self, serial: str): return self._session.get(metadata, resource) + + def updateDeviceCameraVideoSettings(self, serial: str, **kwargs): """ **Update video settings for the given camera** @@ -359,6 +382,8 @@ def updateDeviceCameraVideoSettings(self, serial: str, **kwargs): return self._session.put(metadata, resource, payload) + + def getDeviceCameraVideoLink(self, serial: str, **kwargs): """ **Returns video link to the specified camera** @@ -382,6 +407,8 @@ def getDeviceCameraVideoLink(self, serial: str, **kwargs): return self._session.get(metadata, resource, params) + + def getDeviceCameraWirelessProfiles(self, serial: str): """ **Returns wireless profile assigned to the given camera** @@ -399,6 +426,8 @@ def getDeviceCameraWirelessProfiles(self, serial: str): return self._session.get(metadata, resource) + + def updateDeviceCameraWirelessProfiles(self, serial: str, ids: dict): """ **Assign wireless profiles to the given camera** @@ -422,6 +451,8 @@ def updateDeviceCameraWirelessProfiles(self, serial: str, ids: dict): return self._session.put(metadata, resource, payload) + + def getNetworkCameraQualityRetentionProfiles(self, networkId: str): """ **List the quality retention profiles for this network** @@ -439,6 +470,8 @@ def getNetworkCameraQualityRetentionProfiles(self, networkId: str): return self._session.get(metadata, resource) + + def createNetworkCameraQualityRetentionProfile(self, networkId: str, name: str, **kwargs): """ **Creates new quality retention profile for this network.** @@ -466,13 +499,13 @@ def createNetworkCameraQualityRetentionProfile(self, networkId: str, name: str, networkId = urllib.parse.quote(str(networkId), safe='') resource = f'/networks/{networkId}/camera/qualityRetentionProfiles' - body_params = ['name', 'motionBasedRetentionEnabled', 'restrictedBandwidthModeEnabled', 'audioRecordingEnabled', - 'cloudArchiveEnabled', 'motionDetectorVersion', 'smartRetention', 'scheduleId', - 'maxRetentionDays', 'videoSettings', ] + body_params = ['name', 'motionBasedRetentionEnabled', 'restrictedBandwidthModeEnabled', 'audioRecordingEnabled', 'cloudArchiveEnabled', 'motionDetectorVersion', 'smartRetention', 'scheduleId', 'maxRetentionDays', 'videoSettings', ] payload = {k.strip(): v for k, v in kwargs.items() if k.strip() in body_params} return self._session.post(metadata, resource, payload) + + def getNetworkCameraQualityRetentionProfile(self, networkId: str, qualityRetentionProfileId: str): """ **Retrieve a single quality retention profile** @@ -492,6 +525,8 @@ def getNetworkCameraQualityRetentionProfile(self, networkId: str, qualityRetenti return self._session.get(metadata, resource) + + def updateNetworkCameraQualityRetentionProfile(self, networkId: str, qualityRetentionProfileId: str, **kwargs): """ **Update an existing quality retention profile for this network.** @@ -521,13 +556,13 @@ def updateNetworkCameraQualityRetentionProfile(self, networkId: str, qualityRete qualityRetentionProfileId = urllib.parse.quote(str(qualityRetentionProfileId), safe='') resource = f'/networks/{networkId}/camera/qualityRetentionProfiles/{qualityRetentionProfileId}' - body_params = ['name', 'motionBasedRetentionEnabled', 'restrictedBandwidthModeEnabled', 'audioRecordingEnabled', - 'cloudArchiveEnabled', 'motionDetectorVersion', 'smartRetention', 'scheduleId', - 'maxRetentionDays', 'videoSettings', ] + body_params = ['name', 'motionBasedRetentionEnabled', 'restrictedBandwidthModeEnabled', 'audioRecordingEnabled', 'cloudArchiveEnabled', 'motionDetectorVersion', 'smartRetention', 'scheduleId', 'maxRetentionDays', 'videoSettings', ] payload = {k.strip(): v for k, v in kwargs.items() if k.strip() in body_params} return self._session.put(metadata, resource, payload) + + def deleteNetworkCameraQualityRetentionProfile(self, networkId: str, qualityRetentionProfileId: str): """ **Delete an existing quality retention profile for this network.** @@ -547,6 +582,8 @@ def deleteNetworkCameraQualityRetentionProfile(self, networkId: str, qualityRete return self._session.delete(metadata, resource) + + def getNetworkCameraSchedules(self, networkId: str): """ **Returns a list of all camera recording schedules.** @@ -564,6 +601,8 @@ def getNetworkCameraSchedules(self, networkId: str): return self._session.get(metadata, resource) + + def createNetworkCameraWirelessProfile(self, networkId: str, name: str, ssid: dict, **kwargs): """ **Creates a new camera wireless profile for this network.** @@ -589,6 +628,8 @@ def createNetworkCameraWirelessProfile(self, networkId: str, name: str, ssid: di return self._session.post(metadata, resource, payload) + + def getNetworkCameraWirelessProfiles(self, networkId: str): """ **List the camera wireless profiles for this network.** @@ -606,6 +647,8 @@ def getNetworkCameraWirelessProfiles(self, networkId: str): return self._session.get(metadata, resource) + + def getNetworkCameraWirelessProfile(self, networkId: str, wirelessProfileId: str): """ **Retrieve a single camera wireless profile.** @@ -625,6 +668,8 @@ def getNetworkCameraWirelessProfile(self, networkId: str, wirelessProfileId: str return self._session.get(metadata, resource) + + def updateNetworkCameraWirelessProfile(self, networkId: str, wirelessProfileId: str, **kwargs): """ **Update an existing camera wireless profile in this network.** @@ -652,6 +697,8 @@ def updateNetworkCameraWirelessProfile(self, networkId: str, wirelessProfileId: return self._session.put(metadata, resource, payload) + + def deleteNetworkCameraWirelessProfile(self, networkId: str, wirelessProfileId: str): """ **Delete an existing camera wireless profile for this network.** @@ -671,6 +718,8 @@ def deleteNetworkCameraWirelessProfile(self, networkId: str, wirelessProfileId: return self._session.delete(metadata, resource) + + def getOrganizationCameraBoundariesAreasByDevice(self, organizationId: str, **kwargs): """ **Returns all configured area boundaries of cameras** @@ -700,6 +749,8 @@ def getOrganizationCameraBoundariesAreasByDevice(self, organizationId: str, **kw return self._session.get(metadata, resource, params) + + def getOrganizationCameraBoundariesLinesByDevice(self, organizationId: str, **kwargs): """ **Returns all configured crossingline boundaries of cameras** @@ -729,6 +780,8 @@ def getOrganizationCameraBoundariesLinesByDevice(self, organizationId: str, **kw return self._session.get(metadata, resource, params) + + def getOrganizationCameraCustomAnalyticsArtifacts(self, organizationId: str): """ **List Custom Analytics Artifacts** @@ -746,6 +799,8 @@ def getOrganizationCameraCustomAnalyticsArtifacts(self, organizationId: str): return self._session.get(metadata, resource) + + def createOrganizationCameraCustomAnalyticsArtifact(self, organizationId: str, **kwargs): """ **Create custom analytics artifact** @@ -769,6 +824,8 @@ def createOrganizationCameraCustomAnalyticsArtifact(self, organizationId: str, * return self._session.post(metadata, resource, payload) + + def getOrganizationCameraCustomAnalyticsArtifact(self, organizationId: str, artifactId: str): """ **Get Custom Analytics Artifact** @@ -788,6 +845,8 @@ def getOrganizationCameraCustomAnalyticsArtifact(self, organizationId: str, arti return self._session.get(metadata, resource) + + def deleteOrganizationCameraCustomAnalyticsArtifact(self, organizationId: str, artifactId: str): """ **Delete Custom Analytics Artifact** @@ -807,8 +866,9 @@ def deleteOrganizationCameraCustomAnalyticsArtifact(self, organizationId: str, a return self._session.delete(metadata, resource) - def getOrganizationCameraDetectionsHistoryByBoundaryByInterval(self, organizationId: str, boundaryIds: list, - total_pages=1, direction='next', **kwargs): + + + def getOrganizationCameraDetectionsHistoryByBoundaryByInterval(self, organizationId: str, boundaryIds: list, total_pages=1, direction='next', **kwargs): """ **Returns analytics data for timespans** https://developer.cisco.com/meraki/api-v1/#!get-organization-camera-detections-history-by-boundary-by-interval @@ -842,6 +902,8 @@ def getOrganizationCameraDetectionsHistoryByBoundaryByInterval(self, organizatio return self._session.get_pages(metadata, resource, params, total_pages, direction) + + def getOrganizationCameraOnboardingStatuses(self, organizationId: str, **kwargs): """ **Fetch onboarding status of cameras** @@ -872,6 +934,8 @@ def getOrganizationCameraOnboardingStatuses(self, organizationId: str, **kwargs) return self._session.get(metadata, resource, params) + + def updateOrganizationCameraOnboardingStatuses(self, organizationId: str, **kwargs): """ **Notify that credential handoff to camera has completed** @@ -896,6 +960,8 @@ def updateOrganizationCameraOnboardingStatuses(self, organizationId: str, **kwar return self._session.put(metadata, resource, payload) + + def getOrganizationCameraPermissions(self, organizationId: str): """ **List the permissions scopes for this organization** @@ -913,6 +979,8 @@ def getOrganizationCameraPermissions(self, organizationId: str): return self._session.get(metadata, resource) + + def getOrganizationCameraPermission(self, organizationId: str, permissionScopeId: str): """ **Retrieve a single permission scope** @@ -932,6 +1000,8 @@ def getOrganizationCameraPermission(self, organizationId: str, permissionScopeId return self._session.get(metadata, resource) + + def getOrganizationCameraRoles(self, organizationId: str): """ **List all the roles in this organization** @@ -949,6 +1019,8 @@ def getOrganizationCameraRoles(self, organizationId: str): return self._session.get(metadata, resource) + + def createOrganizationCameraRole(self, organizationId: str, name: str, **kwargs): """ **Creates new role for this organization.** @@ -975,6 +1047,8 @@ def createOrganizationCameraRole(self, organizationId: str, name: str, **kwargs) return self._session.post(metadata, resource, payload) + + def getOrganizationCameraRole(self, organizationId: str, roleId: str): """ **Retrieve a single role.** @@ -994,6 +1068,8 @@ def getOrganizationCameraRole(self, organizationId: str, roleId: str): return self._session.get(metadata, resource) + + def deleteOrganizationCameraRole(self, organizationId: str, roleId: str): """ **Delete an existing role for this organization.** @@ -1013,6 +1089,8 @@ def deleteOrganizationCameraRole(self, organizationId: str, roleId: str): return self._session.delete(metadata, resource) + + def updateOrganizationCameraRole(self, organizationId: str, roleId: str, **kwargs): """ **Update an existing role in this organization.** @@ -1040,4 +1118,3 @@ def updateOrganizationCameraRole(self, organizationId: str, roleId: str, **kwarg payload = {k.strip(): v for k, v in kwargs.items() if k.strip() in body_params} return self._session.put(metadata, resource, payload) - From 3c071840cf42eedf263c383e7e53f98ef8b42385 Mon Sep 17 00:00:00 2001 From: iancart Date: Mon, 21 Apr 2025 13:43:46 -0400 Subject: [PATCH 3/4] Added support for the "array of objects" query parameter type. --- meraki/rest_session.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/meraki/rest_session.py b/meraki/rest_session.py index 697ff4e..da31987 100644 --- a/meraki/rest_session.py +++ b/meraki/rest_session.py @@ -6,12 +6,52 @@ from datetime import datetime import requests +from requests.utils import to_key_val_list +from requests.compat import basestring, urlencode from meraki.__init__ import __version__ from meraki.common import * from meraki.config import * +def encode_params(_, data): + """Encode parameters in a piece of data. + + Will successfully encode parameters when passed as a dict or a list of + 2-tuples. Order is retained if data is a list of 2-tuples but arbitrary + if parameters are supplied as a dict. + """ + if isinstance(data, (str, bytes)): + return data + elif hasattr(data, "read"): + return data + elif hasattr(data, "__iter__"): + result = [] + for k, vs in to_key_val_list(data): + if isinstance(vs, basestring) or not hasattr(vs, "__iter__"): + vs = [vs] + for v in vs: + if v is not None and not isinstance(v, dict): + result.append( + ( + k.encode("utf-8") if isinstance(k, str) else k, + v.encode("utf-8") if isinstance(v, str) else v, + ) + ) + else: + for k_1, v_1 in v.items(): + result.append( + ( + (k + k_1).encode("utf-8") if isinstance(k, str) else k_1, + (v + v_1).encode("utf-8") if isinstance(v, str) else v_1, + ) + ) + return urlencode(result, doseq=True) + else: + return data + +requests.models.RequestEncodingMixin._encode_params = encode_params + def user_agent_extended(be_geo_id, caller): # Generate extended portion of the User-Agent user_agent = dict() From c2ebbf551946cb7253376644a5fa46821a0dacbb Mon Sep 17 00:00:00 2001 From: iancart Date: Mon, 21 Apr 2025 13:46:29 -0400 Subject: [PATCH 4/4] Added support for the "array of objects" query parameter type. --- meraki/api/camera.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/meraki/api/camera.py b/meraki/api/camera.py index 4841b73..f1c97b3 100644 --- a/meraki/api/camera.py +++ b/meraki/api/camera.py @@ -891,10 +891,10 @@ def getOrganizationCameraDetectionsHistoryByBoundaryByInterval(self, organizatio organizationId = urllib.parse.quote(str(organizationId), safe='') resource = f'/organizations/{organizationId}/camera/detections/history/byBoundary/byInterval' - query_params = ['boundaryIds', 'duration', 'perPage', 'boundaryTypes', 'ranges'] + query_params = ['boundaryIds', 'duration', 'perPage', 'boundaryTypes', ] params = {k.strip(): v for k, v in kwargs.items() if k.strip() in query_params} - array_params = ['boundaryIds', 'boundaryTypes', 'ranges'] + array_params = ['boundaryIds', 'boundaryTypes', ] for k, v in kwargs.items(): if k.strip() in array_params: params[f'{k.strip()}[]'] = kwargs[f'{k}']