class ScopeState(base.BaseResource): @api_utils.paginated @api_utils.add_input_schema( 'query', { voluptuous.Optional('scope_id', default=[]): api_utils.MultiQueryParam(str), voluptuous.Optional('scope_key', default=[]): api_utils.MultiQueryParam(str), voluptuous.Optional('fetcher', default=[]): api_utils.MultiQueryParam(str), voluptuous.Optional('collector', default=[]): api_utils.MultiQueryParam(str), }) @api_utils.add_output_schema({ 'results': [{ voluptuous.Required('scope_id'): api_utils.get_string_type(), voluptuous.Required('scope_key'): api_utils.get_string_type(), voluptuous.Required('fetcher'): api_utils.get_string_type(), voluptuous.Required('collector'): api_utils.get_string_type(), voluptuous.Required('state'): api_utils.get_string_type(), }] }) def get(self, offset=0, limit=100, scope_id=None, scope_key=None, fetcher=None, collector=None): policy.authorize( flask.request.context, 'scope:get_state', {'tenant_id': scope_id or flask.request.context.project_id}) results = storage_state.StateManager().get_all( identifier=scope_id, scope_key=scope_key, fetcher=fetcher, collector=collector, offset=offset, limit=limit, ) if len(results) < 1: raise http_exceptions.NotFound( "No resource found for provided filters.") return { 'results': [{ 'scope_id': r.identifier, 'scope_key': r.scope_key, 'fetcher': r.fetcher, 'collector': r.collector, 'state': str(r.state), } for r in results] }
class ReprocessSchedulerGetApi(base.BaseResource): def __init__(self, *args, **kwargs): super(ReprocessSchedulerGetApi, self).__init__(*args, **kwargs) self.schedule_reprocessing_db = storage_state.ReprocessingSchedulerDb() @api_utils.paginated @api_utils.add_input_schema('query', { voluptuous.Optional('scope_ids'): api_utils.MultiQueryParam(str), voluptuous.Optional('order', default="desc"): api_utils.SingleQueryParam(str) }) @api_utils.add_output_schema({'results': [{ voluptuous.Required('reason'): validation_utils.get_string_type(), voluptuous.Required('scope_id'): validation_utils.get_string_type(), voluptuous.Required('start_reprocess_time'): validation_utils.get_string_type(), voluptuous.Required('end_reprocess_time'): validation_utils.get_string_type(), voluptuous.Required('current_reprocess_time'): validation_utils.get_string_type(), }]}) def get(self, scope_ids=[], path_scope_id=None, offset=0, limit=100, order="desc"): if path_scope_id and scope_ids: LOG.warning("Filtering by scope IDs [%s] and path scope ID [%s] " "does not make sense. You should use only one of " "them. We will use only the path scope ID for this " "request.", scope_ids, path_scope_id) if path_scope_id: scope_ids = [path_scope_id] policy.authorize( flask.request.context, 'schedule:get_task_reprocesses', {'tenant_id': flask.request.context.project_id or scope_ids} ) if not isinstance(scope_ids, list): scope_ids = [scope_ids] if order not in ACCEPTED_GET_REPROCESSING_REQUEST_ORDERS: raise http_exceptions.BadRequest( "The order [%s] is not valid. Accepted values are %s.", order, ACCEPTED_GET_REPROCESSING_REQUEST_ORDERS) schedules = self.schedule_reprocessing_db.get_all( identifier=scope_ids, remove_finished=False, offset=offset, limit=limit, order=order) return { 'results': [{ 'scope_id': s.identifier, 'reason': s.reason, 'start_reprocess_time': s.start_reprocess_time.isoformat(), 'end_reprocess_time': s.end_reprocess_time.isoformat(), 'current_reprocess_time': s.current_reprocess_time.isoformat() if s.current_reprocess_time else "", } for s in schedules]}
class Summary(base.BaseResource): """Resource allowing to retrieve a rating summary.""" @api_utils.paginated @api_utils.add_input_schema( 'query', { voluptuous.Optional('groupby'): api_utils.MultiQueryParam(str), voluptuous.Optional('filters'): api_utils.SingleDictQueryParam(str, str), voluptuous.Optional('begin'): api_utils.SingleQueryParam(tzutils.dt_from_iso), voluptuous.Optional('end'): api_utils.SingleQueryParam(tzutils.dt_from_iso), }) def get(self, groupby=None, filters={}, begin=None, end=None, offset=0, limit=100): policy.authorize(flask.request.context, 'summary:get_summary', {'project_id': flask.request.context.project_id}) begin = begin or tzutils.get_month_start() end = end or tzutils.get_next_month() if not flask.request.context.is_admin: if flask.request.context.project_id is None: # Unscoped non-admin user return { 'total': 0, 'columns': [], 'results': [], } filters['project_id'] = flask.request.context.project_id metric_types = [filters.pop('type')] if 'type' in filters else None total = self._storage.total( begin=begin, end=end, groupby=groupby, filters=filters, metric_types=metric_types, offset=offset, limit=limit, paginate=True, ) columns = [] if len(total['results']) > 0: columns = list(total['results'][0].keys()) return { 'total': total['total'], 'columns': columns, 'results': [list(res.values()) for res in total['results']] }
class ScopeState(base.BaseResource): @classmethod def reload(cls): super(ScopeState, cls).reload() cls._client = messaging.get_client() cls._storage_state = storage_state.StateManager() @api_utils.paginated @api_utils.add_input_schema( 'query', { voluptuous.Optional('scope_id', default=[]): api_utils.MultiQueryParam(str), voluptuous.Optional('scope_key', default=[]): api_utils.MultiQueryParam(str), voluptuous.Optional('fetcher', default=[]): api_utils.MultiQueryParam(str), voluptuous.Optional('collector', default=[]): api_utils.MultiQueryParam(str), voluptuous.Optional('active', default=[]): api_utils.MultiQueryParam(int), }) @api_utils.add_output_schema({ 'results': [{ voluptuous.Required('scope_id'): vutils.get_string_type(), voluptuous.Required('scope_key'): vutils.get_string_type(), voluptuous.Required('fetcher'): vutils.get_string_type(), voluptuous.Required('collector'): vutils.get_string_type(), voluptuous.Optional('last_processed_timestamp'): vutils.get_string_type(), # This "state" property should be removed in the next release. voluptuous.Optional('state'): vutils.get_string_type(), voluptuous.Required('active'): bool, voluptuous.Optional('scope_activation_toggle_date'): vutils.get_string_type(), }] }) def get(self, offset=0, limit=100, scope_id=None, scope_key=None, fetcher=None, collector=None, active=None): policy.authorize( flask.request.context, 'scope:get_state', {'project_id': scope_id or flask.request.context.project_id}) results = self._storage_state.get_all(identifier=scope_id, scope_key=scope_key, fetcher=fetcher, collector=collector, offset=offset, limit=limit, active=active) if len(results) < 1: raise http_exceptions.NotFound( "No resource found for provided filters.") return { 'results': [{ 'scope_id': r.identifier, 'scope_key': r.scope_key, 'fetcher': r.fetcher, 'collector': r.collector, 'state': r.last_processed_timestamp.isoformat(), 'last_processed_timestamp': r.last_processed_timestamp.isoformat(), 'active': r.active, 'scope_activation_toggle_date': r.scope_activation_toggle_date.isoformat() if r.scope_activation_toggle_date else None } for r in results] } @api_utils.add_input_schema( 'body', { voluptuous.Exclusive('all_scopes', 'scope_selector'): voluptuous.Boolean(), voluptuous.Exclusive('scope_id', 'scope_selector'): api_utils.MultiQueryParam(str), voluptuous.Optional('scope_key', default=[]): api_utils.MultiQueryParam(str), voluptuous.Optional('fetcher', default=[]): api_utils.MultiQueryParam(str), voluptuous.Optional('collector', default=[]): api_utils.MultiQueryParam(str), voluptuous.Optional('last_processed_timestamp'): voluptuous.Coerce(tzutils.dt_from_iso), # This "state" property should be removed in the next release. voluptuous.Optional('state'): voluptuous.Coerce(tzutils.dt_from_iso), }) def put(self, all_scopes=False, scope_id=None, scope_key=None, fetcher=None, collector=None, last_processed_timestamp=None, state=None): policy.authorize( flask.request.context, 'scope:reset_state', {'project_id': scope_id or flask.request.context.project_id}) if not all_scopes and scope_id is None: raise http_exceptions.BadRequest( "Either all_scopes or a scope_id should be specified.") if not state and not last_processed_timestamp: raise http_exceptions.BadRequest( "Variables 'state' and 'last_processed_timestamp' cannot be " "empty/None. We expect at least one of them.") if state: LOG.warning("The use of 'state' variable is deprecated, and will " "be removed in the next upcomming release. You should " "consider using 'last_processed_timestamp' variable.") results = self._storage_state.get_all( identifier=scope_id, scope_key=scope_key, fetcher=fetcher, collector=collector, ) if len(results) < 1: raise http_exceptions.NotFound( "No resource found for provided filters.") serialized_results = [{ 'scope_id': r.identifier, 'scope_key': r.scope_key, 'fetcher': r.fetcher, 'collector': r.collector, } for r in results] if not last_processed_timestamp: last_processed_timestamp = state self._client.cast({}, 'reset_state', res_data={ 'scopes': serialized_results, 'last_processed_timestamp': last_processed_timestamp.isoformat() }) return {}, 202 @api_utils.add_input_schema( 'body', { voluptuous.Required('scope_id'): api_utils.SingleQueryParam(str), voluptuous.Optional('scope_key'): api_utils.SingleQueryParam(str), voluptuous.Optional('fetcher'): api_utils.SingleQueryParam(str), voluptuous.Optional('collector'): api_utils.SingleQueryParam(str), voluptuous.Optional('active'): api_utils.SingleQueryParam(bool), }) @api_utils.add_output_schema({ voluptuous.Required('scope_id'): vutils.get_string_type(), voluptuous.Required('scope_key'): vutils.get_string_type(), voluptuous.Required('fetcher'): vutils.get_string_type(), voluptuous.Required('collector'): vutils.get_string_type(), # This "state" property should be removed in the next release. voluptuous.Required('state'): vutils.get_string_type(), voluptuous.Optional('last_processed_timestamp'): voluptuous.Coerce(tzutils.dt_from_iso), voluptuous.Required('active'): bool, voluptuous.Required('scope_activation_toggle_date'): vutils.get_string_type() }) def patch(self, scope_id, scope_key=None, fetcher=None, collector=None, active=None): policy.authorize( flask.request.context, 'scope:patch_state', {'tenant_id': scope_id or flask.request.context.project_id}) results = self._storage_state.get_all(identifier=scope_id, active=None) if len(results) < 1: raise http_exceptions.NotFound( "No resource found for provided filters.") if len(results) > 1: LOG.debug( "Too many resources found with the same scope_id [%s], " "scopes found: [%s].", scope_id, results) raise http_exceptions.NotFound("Too many resources found with " "the same scope_id: %s." % scope_id) scope_to_update = results[0] LOG.debug("Executing update of storage scope: [%s].", scope_to_update) self._storage_state.update_storage_scope(scope_to_update, scope_key=scope_key, fetcher=fetcher, collector=collector, active=active) storage_scopes = self._storage_state.get_all(identifier=scope_id, active=active) update_storage_scope = storage_scopes[0] return { 'scope_id': update_storage_scope.identifier, 'scope_key': update_storage_scope.scope_key, 'fetcher': update_storage_scope.fetcher, 'collector': update_storage_scope.collector, 'state': update_storage_scope.state.isoformat(), 'last_processed_timestamp': update_storage_scope.last_processed_timestamp.isoformat(), 'active': update_storage_scope.active, 'scope_activation_toggle_date': update_storage_scope.scope_activation_toggle_date.isoformat() } @api_utils.add_input_schema( 'body', { voluptuous.Required('scope_id'): api_utils.SingleQueryParam(str), voluptuous.Optional('scope_key'): api_utils.SingleQueryParam(str), voluptuous.Optional('fetcher'): api_utils.SingleQueryParam(str), voluptuous.Optional('collector'): api_utils.SingleQueryParam(str), voluptuous.Optional('active'): api_utils.SingleQueryParam(bool), }) @api_utils.add_output_schema({ voluptuous.Required('scope_id'): vutils.get_string_type(), voluptuous.Required('scope_key'): vutils.get_string_type(), voluptuous.Required('fetcher'): vutils.get_string_type(), voluptuous.Required('collector'): vutils.get_string_type(), # This "state" property should be removed in the next release. voluptuous.Required('state'): vutils.get_string_type(), voluptuous.Optional('last_processed_timestamp'): voluptuous.Coerce(tzutils.dt_from_iso), voluptuous.Required('active'): bool, voluptuous.Required('scope_activation_toggle_date'): vutils.get_string_type() }) def post(self, scope_id, scope_key=None, fetcher=None, collector=None, active=None): policy.authorize( flask.request.context, 'scope:post_state', {'tenant_id': scope_id or flask.request.context.project_id}) results = self._storage_state.get_all(identifier=scope_id) if len(results) >= 1: LOG.debug( "There is already a scope with ID [%s], " "scopes found: [%s].", scope_id, results) raise http_exceptions.NotFound("Cannot create a scope with an " "already existing scope_id: %s." % scope_id) LOG.debug( "Creating storage scope with data: [scope_id=%s, " "scope_key=%s, fetcher=%s, collector=%s, active=%s].", scope_id, scope_key, fetcher, collector, active) self._storage_state.create_scope(scope_id, None, fetcher=fetcher, collector=collector, scope_key=scope_key, active=active) storage_scopes = self._storage_state.get_all(identifier=scope_id) update_storage_scope = storage_scopes[0] last_processed_timestamp = None if update_storage_scope.last_processed_timestamp.isoformat(): last_processed_timestamp =\ update_storage_scope.last_processed_timestamp.isoformat() return { 'scope_id': update_storage_scope.identifier, 'scope_key': update_storage_scope.scope_key, 'fetcher': update_storage_scope.fetcher, 'collector': update_storage_scope.collector, 'state': last_processed_timestamp, 'last_processed_timestamp': last_processed_timestamp, 'active': update_storage_scope.active, 'scope_activation_toggle_date': update_storage_scope.scope_activation_toggle_date.isoformat() }
class ScopeState(base.BaseResource): def __init__(self, *args, **kwargs): super(ScopeState, self).__init__(*args, **kwargs) self._client = messaging.get_client() self._storage_state = storage_state.StateManager() @api_utils.paginated @api_utils.add_input_schema( 'query', { voluptuous.Optional('scope_id', default=[]): api_utils.MultiQueryParam(str), voluptuous.Optional('scope_key', default=[]): api_utils.MultiQueryParam(str), voluptuous.Optional('fetcher', default=[]): api_utils.MultiQueryParam(str), voluptuous.Optional('collector', default=[]): api_utils.MultiQueryParam(str), }) @api_utils.add_output_schema({ 'results': [{ voluptuous.Required('scope_id'): vutils.get_string_type(), voluptuous.Required('scope_key'): vutils.get_string_type(), voluptuous.Required('fetcher'): vutils.get_string_type(), voluptuous.Required('collector'): vutils.get_string_type(), voluptuous.Required('state'): vutils.get_string_type(), }] }) def get(self, offset=0, limit=100, scope_id=None, scope_key=None, fetcher=None, collector=None): policy.authorize( flask.request.context, 'scope:get_state', {'tenant_id': scope_id or flask.request.context.project_id}) results = self._storage_state.get_all( identifier=scope_id, scope_key=scope_key, fetcher=fetcher, collector=collector, offset=offset, limit=limit, ) if len(results) < 1: raise http_exceptions.NotFound( "No resource found for provided filters.") return { 'results': [{ 'scope_id': r.identifier, 'scope_key': r.scope_key, 'fetcher': r.fetcher, 'collector': r.collector, 'state': r.state.isoformat(), } for r in results] } @api_utils.add_input_schema( 'body', { voluptuous.Exclusive('all_scopes', 'scope_selector'): voluptuous.Boolean(), voluptuous.Exclusive('scope_id', 'scope_selector'): api_utils.MultiQueryParam(str), voluptuous.Optional('scope_key', default=[]): api_utils.MultiQueryParam(str), voluptuous.Optional('fetcher', default=[]): api_utils.MultiQueryParam(str), voluptuous.Optional('collector', default=[]): api_utils.MultiQueryParam(str), voluptuous.Required('state'): voluptuous.Coerce(tzutils.dt_from_iso), }) def put(self, all_scopes=False, scope_id=None, scope_key=None, fetcher=None, collector=None, state=None): policy.authorize( flask.request.context, 'scope:reset_state', {'tenant_id': scope_id or flask.request.context.project_id}) if not all_scopes and scope_id is None: raise http_exceptions.BadRequest( "Either all_scopes or a scope_id should be specified.") results = self._storage_state.get_all( identifier=scope_id, scope_key=scope_key, fetcher=fetcher, collector=collector, ) if len(results) < 1: raise http_exceptions.NotFound( "No resource found for provided filters.") serialized_results = [{ 'scope_id': r.identifier, 'scope_key': r.scope_key, 'fetcher': r.fetcher, 'collector': r.collector, } for r in results] self._client.cast({}, 'reset_state', res_data={ 'scopes': serialized_results, 'state': state.isoformat(), }) return {}, 202
class Summary(base.BaseResource): """Resource allowing to retrieve a rating summary.""" @api_utils.paginated @api_utils.add_input_schema( 'query', { voluptuous.Optional('response_format'): api_utils.SingleQueryParam(str), voluptuous.Optional('custom_fields'): api_utils.SingleQueryParam(str), voluptuous.Optional('groupby'): api_utils.MultiQueryParam(str), voluptuous.Optional('filters'): api_utils.MultiDictQueryParam(str, str), voluptuous.Optional('begin'): api_utils.SingleQueryParam(tzutils.dt_from_iso), voluptuous.Optional('end'): api_utils.SingleQueryParam(tzutils.dt_from_iso), }) def get(self, response_format=TABLE_RESPONSE_FORMAT, custom_fields=None, groupby=None, filters={}, begin=None, end=None, offset=0, limit=100): if response_format not in ALL_RESPONSE_FORMATS: raise voluptuous.Invalid("Invalid response format [%s]. Valid " "format are [%s]." % (response_format, ALL_RESPONSE_FORMATS)) policy.authorize(flask.request.context, 'summary:get_summary', {'project_id': flask.request.context.project_id}) begin = begin or tzutils.get_month_start() end = end or tzutils.get_next_month() if not flask.request.context.is_admin: if flask.request.context.project_id is None: # Unscoped non-admin user return { 'total': 0, 'columns': [], 'results': [], } filters['project_id'] = flask.request.context.project_id metric_types = filters.pop('type', []) if not isinstance(metric_types, list): metric_types = [metric_types] arguments = { 'begin': begin, 'end': end, 'groupby': groupby, 'filters': filters, 'metric_types': metric_types, 'offset': offset, 'limit': limit, 'paginate': True } if custom_fields: arguments['custom_fields'] = custom_fields total = self._storage.total(**arguments) return self.generate_response(response_format, total) @staticmethod def generate_response(response_format, total): response = {'total': total['total']} if response_format == TABLE_RESPONSE_FORMAT: columns = [] if len(total['results']) > 0: columns = list(total['results'][0].keys()) response['columns'] = columns response['results'] = [ list(res.values()) for res in total['results'] ] elif response_format == OBJECT_RESPONSE_FORMAT: response['results'] = total['results'] response['format'] = response_format return response
class ReprocessSchedulerPostApi(base.BaseResource): def __init__(self, *args, **kwargs): super(ReprocessSchedulerPostApi, self).__init__(*args, **kwargs) self.storage_state_manager = storage_state.StateManager() self.schedule_reprocessing_db = storage_state.ReprocessingSchedulerDb() @api_utils.add_input_schema('body', { voluptuous.Required('scope_ids'): api_utils.MultiQueryParam(str), voluptuous.Required('start_reprocess_time'): voluptuous.Coerce(dt_from_iso_as_utc), voluptuous.Required('end_reprocess_time'): voluptuous.Coerce(dt_from_iso_as_utc), voluptuous.Required('reason'): api_utils.SingleQueryParam(str), }) def post(self, scope_ids=[], start_reprocess_time=None, end_reprocess_time=None, reason=None): policy.authorize( flask.request.context, 'schedule:task_reprocesses', {'tenant_id': flask.request.context.project_id or scope_ids} ) ReprocessSchedulerPostApi.validate_inputs( end_reprocess_time, reason, scope_ids, start_reprocess_time) if ALL_SCOPES_OPTION in scope_ids: scope_ids = [] if not isinstance(scope_ids, list): scope_ids = [scope_ids] all_scopes_to_reprocess = self.storage_state_manager.get_all( identifier=scope_ids, offset=None, limit=None) ReprocessSchedulerPostApi.check_if_there_are_invalid_scopes( all_scopes_to_reprocess, end_reprocess_time, scope_ids, start_reprocess_time) ReprocessSchedulerPostApi.validate_start_end_for_reprocessing( all_scopes_to_reprocess, end_reprocess_time, start_reprocess_time) self.validate_reprocessing_schedules_overlaps( all_scopes_to_reprocess, end_reprocess_time, start_reprocess_time) for scope in all_scopes_to_reprocess: schedule = ReprocessingScheduler( identifier=scope.identifier, reason=reason, start_reprocess_time=start_reprocess_time, end_reprocess_time=end_reprocess_time) LOG.debug("Persisting scope reprocessing schedule [%s].", schedule) self.schedule_reprocessing_db.persist(schedule) return {}, 202 @staticmethod def validate_inputs( end_reprocess_time, reason, scope_ids, start_reprocess_time): ReprocessSchedulerPostApi.validate_scope_ids(scope_ids) if not reason.strip(): raise http_exceptions.BadRequest( "Empty or blank reason text is not allowed. Please, do " "inform/register the reason for the reprocessing of a " "previously processed timestamp.") if end_reprocess_time < start_reprocess_time: raise http_exceptions.BadRequest( "End reprocessing timestamp [%s] cannot be less than " "start reprocessing timestamp [%s]." % (start_reprocess_time, end_reprocess_time)) @staticmethod def validate_scope_ids(scope_ids): option_all_selected = False for s in scope_ids: if s == ALL_SCOPES_OPTION: option_all_selected = True continue if option_all_selected and len(scope_ids) != 1: raise http_exceptions.BadRequest( "Cannot use 'ALL' with scope ID [%s]. Either schedule a " "reprocessing for all active scopes using 'ALL' option, " "or inform only the scopes you desire to schedule a " "reprocessing." % scope_ids) @staticmethod def check_if_there_are_invalid_scopes( all_scopes_to_reprocess, end_reprocess_time, scope_ids, start_reprocess_time): invalid_scopes = [] for s in scope_ids: scope_exist_in_db = False for scope_to_reprocess in all_scopes_to_reprocess: if s == scope_to_reprocess.identifier: scope_exist_in_db = True break if not scope_exist_in_db: invalid_scopes.append(s) if invalid_scopes: raise http_exceptions.BadRequest( "Scopes %s scheduled to reprocess [start=%s, end=%s] " "do not exist." % (invalid_scopes, start_reprocess_time, end_reprocess_time)) @staticmethod def validate_start_end_for_reprocessing(all_scopes_to_reprocess, end_reprocess_time, start_reprocess_time): for scope in all_scopes_to_reprocess: last_processed_timestamp = scope.last_processed_timestamp if start_reprocess_time > last_processed_timestamp: raise http_exceptions.BadRequest( "Cannot execute a reprocessing [start=%s] for scope [%s] " "starting after the last possible timestamp [%s]." % (start_reprocess_time, scope, last_processed_timestamp)) if end_reprocess_time > scope.last_processed_timestamp: raise http_exceptions.BadRequest( "Cannot execute a reprocessing [end=%s] for scope [%s] " "ending after the last possible timestamp [%s]." % (end_reprocess_time, scope, last_processed_timestamp)) def validate_reprocessing_schedules_overlaps( self, all_scopes_to_reprocess, end_reprocess_time, start_reprocess_time): scheduling_range = DateTimeRange( start_reprocess_time, end_reprocess_time) for scope_to_reprocess in all_scopes_to_reprocess: all_reprocessing_schedules = self.schedule_reprocessing_db.get_all( identifier=[scope_to_reprocess.identifier]) LOG.debug("All schedules [%s] for reprocessing found for scope " "[%s]", all_reprocessing_schedules, scope_to_reprocess) if not all_reprocessing_schedules: LOG.debug( "No need to validate possible collision of reprocessing " "for scope [%s] because it does not have active " "reprocessing schedules." % scope_to_reprocess) continue for schedule in all_reprocessing_schedules: scheduled_range = DateTimeRange( tzutils.local_to_utc(schedule.start_reprocess_time), tzutils.local_to_utc(schedule.end_reprocess_time)) try: if scheduling_range.is_intersection(scheduled_range): raise http_exceptions.BadRequest( self.generate_overlap_error_message( scheduled_range, scheduling_range, scope_to_reprocess)) except ValueError as e: raise http_exceptions.BadRequest( self.generate_overlap_error_message( scheduled_range, scheduling_range, scope_to_reprocess) + "Error: [%s]." % e) @staticmethod def generate_overlap_error_message(scheduled_range, scheduling_range, scope_to_reprocess): return "Cannot schedule a reprocessing for scope [%s] for " \ "reprocessing time [%s], because it already has a schedule " \ "for a similar time range [%s]." % (scope_to_reprocess, scheduling_range, scheduled_range)