def _preprocess_params(self, user, **kwargs): if 'channel_id' not in kwargs: raise exc.InvalidParameterConfiguration( "Required parameter 'Perform' missing.") if 'content' not in kwargs and 'post_id' not in kwargs: raise exc.InvalidParameterConfiguration( "Either parameter 'content' or 'post_id' required in POST fields." ) try: self.channel = Channel.objects.get(kwargs['channel_id']) except Channel.DoesNotExist: raise exc.ResourceDoesNotExist("No channel found with id=%s" % kwargs['channel_id']) if kwargs.get('post_id', False): post = Post.objects.get(kwargs['post_id']) elif kwargs.get('content', False): target_channel = self.channel.id if not isinstance(self.channel, SmartTagChannel) else \ self.channel.parent_channel post = factory_by_user(user, channels=[target_channel], content=kwargs['content']) else: raise Exception("Either post_id or content should be passed") return post
def validate_action(self, action_json): if not isinstance(action_json, dict): raise exc.InvalidParameterConfiguration(ERR_MSG_INVALID_JSON % (KEY_ACTION, action_json)) if KEY_ACTION_ID not in action_json: raise exc.InvalidParameterConfiguration(ERR_MSG_MISSING_ACTION_ID % action_json)
def _score(self, user, predictor, *args, **kwargs): if predictor.account_id != user.account.id: raise exc.InvalidParameterConfiguration(ERR_MSG_NO_ACCESS % predictor.id) if KEY_ACTIONS not in kwargs and KEY_ACTION_FILTERS not in kwargs: raise exc.InvalidParameterConfiguration( ERR_MSG_MISSING_FIELD % (KEY_ACTIONS + ' or ' + KEY_ACTION_FILTERS)) if KEY_ACTIONS in kwargs: unprocessed_actions = kwargs[KEY_ACTIONS] if not isinstance(unprocessed_actions, list): raise exc.InvalidParameterConfiguration() for action in unprocessed_actions: self.validate_action(action) else: filter_query = kwargs[KEY_ACTION_FILTERS] query, _ = predictor.construct_filter_query( filter_query, context=predictor.get_action_class().fields.keys()) # query['account_id'] = predictor.account_id actions = predictor.get_action_class().objects.coll.find(query) unprocessed_actions = [] for action in actions: attached_data = action if predictor.action_id_expression in action: attached_data[KEY_ACTION_ID] = action[ predictor.action_id_expression] else: attached_data[KEY_ACTION_ID] = str(action['_id']) unprocessed_actions.append(attached_data) for action in unprocessed_actions: self.validate_action(action) action_lower_mappings = dict() for act_key in predictor.action_feature_names: action_lower_mappings[act_key.lower()] = act_key processed_actions = [] action_id_mapping = dict() for action in unprocessed_actions: processed_action = {KEY_ACTION_ID: action[KEY_ACTION_ID]} action_id_mapping[action[KEY_ACTION_ID]] = action for k, v in action.iteritems(): if k.lower() not in action_lower_mappings: continue processed_action[action_lower_mappings[k.lower()]] = v processed_actions.append(processed_action) context = self.get_validated_context(predictor, kwargs) #import pdb; pdb.set_trace() score_start = datetime.utcnow() try: results = predictor.score(processed_actions, context) except AppException, ex: return dict(ok=False, error=str(ex))
def handle_reject(self, user, *args, **kwargs): if 'event_id' not in kwargs: raise exc.InvalidParameterConfiguration("Expected required field 'event_id'") if 'tag_id' not in kwargs: raise exc.InvalidParameterConfiguration("Expected required field 'tag_id'") event_type = kwargs.pop('type', None) model_klass = self.EVENT_MODEL_MAP.get(event_type, Event) evt = model_klass.objects.get(kwargs['event_id']) tag = EventTag.objects.get(kwargs['tag_id']) evt.remove_tag(tag) return self._format_single_doc(evt)
def _search(self, user, *args, **kwargs): error_desc = "Requests should contain a channel id and some query text: {'channel': <>, 'query': <>}" required_params = ['channel', 'query'] LOGGER.debug('Searching for FAQs with params: %s' % kwargs) for entry in required_params: if entry not in kwargs: error_msg = "Missing required parameter '%s'" % entry raise exc.InvalidParameterConfiguration(error_msg, description=error_desc) try: channel = Channel.objects.get(kwargs['channel']) except Channel.DoesNotExist: raise exc.ResourceDoesNotExist( "No channel with id=%s found in the system." % kwargs['channel']) result = FAQ.objects.text_search(channel, kwargs['query'], limit=100) LOGGER.debug('Search results for FAQs: %s' % result) from solariat.utils.timeslot import parse_datetime, now _created = parse_datetime(kwargs.get('_created', now()), default=now()) faq_event = FAQQueryEvent.objects.create_by_user(user, query=kwargs['query'], channels=[channel.id], _created=_created) if 'customer_id' in kwargs: CustomerProfile = user.account.get_customer_profile_class() customer = CustomerProfile.objects.get(kwargs['customer_id']) # import ipdb # ipdb.set_trace() # actions = NextBestActionEngine.upsert(account_id=customer.account_id).search(faq_event, customer) # TODO: THink how we can implement this properly with new advisors API return dict(list=result, event=faq_event.to_dict()) #, actions=actions) else: return dict(list=result, event=faq_event.to_dict())
def __create_agent(self, user, agent_data, AgentProfile, agent_profile_schema): if 'id' in agent_data: try: return self.__update_agent(user, agent_data['id'], agent_data) except AgentProfile.DoesNotExist, ex: raise exc.InvalidParameterConfiguration('No agent with id=%s found in db. Skipped record %s' % (agent_data['id'], agent_data))
def put(self, user, *args, **kwargs): if 'action' not in kwargs: raise exc.InvalidParameterConfiguration( "Parameter 'action' is required") action = kwargs.get('action') if action not in {'stop', 'resume'}: raise exc.ValidationError("Invalid 'action' value") subscription = self.get_subscription(user, kwargs) if action == 'resume': if not subscription.is_resumable: return dict(ok=False, error="Subscription can not be resumed") subscription.process() return dict(ok=True, message="Subscription has been resumed", details=subscription.to_dict()) elif action == 'stop': if not subscription.is_stoppable: return dict(ok=False, error="Subscription can not be stopped") try: result = subscription.stop() except Exception as e: error = unicode(e) if 'Datasift returned error' in error: error = error[-error.rfind('Datasift returned error'):] raise exc.AppException(error) else: return dict(ok=True, message="Subscription has been stopped", details=result)
def _train(self, user, *args, **kwargs): error_desc = "Requests should either contain a list of samples in the form: " error_desc += "{'samples': [(<'faq_id', 'query', 'value'>), ...]}" error_desc += " or a simple entry {'faq_id': <>, 'query': <>, 'value': <>}" if 'samples' in kwargs: # A batch of data passed in errors = [] result = [] for sample in kwargs['samples']: try: result.append(self.__sample_train(*sample)) except FAQ.DoesNotExist: errors.append( "No faq exists with id=%s. Skipping related sample." % sample[0]) result = dict(ok=len(errors) == 0, list=result) result['skipped_samples'] = len(errors) result['errors'] = list(set(errors)) return result else: required_params = ['faq_id', 'query', 'value'] for entry in required_params: if entry not in kwargs: error_msg = "Missing required parameter '%s'" % entry raise exc.InvalidParameterConfiguration( error_msg, description=error_desc) return dict(item=self.__sample_train( kwargs['faq_id'], kwargs['query'], kwargs['value']))
def channel_resolver(channel, platform, logged_in_user): """ Based on input `channel` parameter return an actual Channel entity on our side. :param channel: Required, should be a channel id. :param platform: For consistency across resolvers. Might be used to infer some platform specific restrictions. """ assert platform in ('twitter', 'facebook') if isinstance(channel, Channel): result = channel try: result = Channel.objects.get(channel) except Channel.DoesNotExist: return None if isinstance(result, ServiceChannel): dispatch = result.get_outbound_channel(logged_in_user) if dispatch is None: error = "No dispatch channel could be found for service: %s." % result.title error += " Please set it on the GSA configuration page." raise exc.InvalidParameterConfiguration(error) # This is a ugly hack so we have service channel information on any API calls that actually need it # E.G. get_channel_description. We should strongly consider merging Service and Account channels for facebook dispatch._current_service_channel = result return dispatch return result
def train(self, user, *args, **kwargs): """ Tied to the train endpoint. Accepts POST requests and does a training based on the input samples Sample requests: Generic request by some filter: curl http://staging.socialoptimizr.com/api/v2.0/classifiers/train POST Parameters: :param token: <Required> - A valid user access token :param samples: <Required> - list of samples to classify. Each entry in the list is a dictionary: {'text': "This is a test post", 'values': [(classifier1_id, 1), (classifier2_id, 1)],} Output: A dictionary in the form { "ok": true, "errors": [], "list": [ { "latency": 7, "id": "5423fc8c31eddd17f7f96dc0" }, { "latency": 145, "id": "5423fc8c31eddd17f7f96dbf" } ], "skipped_samples": 0 } In case of missing parameter: { "code": 113, "ok": false, "error": "Missing required parameter 'samples'", "description": "You are missing a required parameter 'samples'. This should be a list of text samples to classify. Each entry should be a simple dictionary {'text': <content>, values: [(classifier1_id, value), (classifier2_id, value), ..]}" } """ if 'samples' not in kwargs: error_msg = "Missing required parameter 'samples'" error_desc = "You are missing a required parameter 'samples'. This should be a list of " error_desc += "text samples to classify. Each entry should be a simple dictionary " error_desc += "{'text': <content>, values: [(classifier1_id, value), (classifier2_id, value), ..]}" raise exc.InvalidParameterConfiguration(error_msg, description=error_desc) errors, latencies = self.__train(kwargs['samples']) result = [] for entry in latencies: result.append({'id': entry, 'latency': latencies[entry]}) result = dict(ok=len(errors) == 0, list=result) result['skipped_samples'] = len(errors) result['errors'] = list(set(errors)) return result
def retrain(self, user, *args, **kwargs): """ Tied to the retrain endpoint. Accepts POST requests and does a retraining of the input classifier ids Sample requests: Generic request by some filter: curl http://staging.socialoptimizr.com/api/v2.0/classifiers/retrain POST Parameters: :param token: <Required> - A valid user access token :param classifiers: <Required>: [list] a list of classifier ids indicating which classifiers to apply Output: A dictionary in the form { "ok": true, "errors": [], "list": [ { "latency": 2, "id": "5424036831eddd1a7624a252" }, { "latency": 2, "id": "5424036731eddd1a7624a251" } ], "skipped_samples": 0 } """ if 'classifiers' not in kwargs: error_msg = "Missing required parameter 'classifiers'." error_desc = "You are missing a required parameter 'classifiers'. This should be a list " error_desc += "of Classifier ids that will be used to compute prediction scores on the sample list." raise exc.InvalidParameterConfiguration(error_msg, description=error_desc) latencies = {} errors = [] for classifier in AuthTextClassifier.objects( id__in=kwargs['classifiers']): batch_start = datetime.utcnow() classifier.retrain() latencies[str(classifier.id)] = (datetime.utcnow() - batch_start).total_seconds() train_latencies = {} errors = [] if 'sample' in kwargs: errors, train_latencies = self.__train(kwargs['samples']) result = [] for entry in latencies: if entry in train_latencies: latencies[entry] += train_latencies[entry] result.append({'id': entry, 'latency': latencies[entry]}) result = dict(ok=len(errors) == 0, list=result) result['skipped_samples'] = len(errors) result['errors'] = list(set(errors)) return result
def feedback(self, user, predictor, *args, **kwargs): if predictor.account_id != user.account.id: raise exc.InvalidParameterConfiguration(ERR_MSG_NO_ACCESS % predictor.id) context = self.get_validated_context(predictor, kwargs) if KEY_ACTION not in kwargs: raise exc.InvalidParameterConfiguration(ERR_MSG_MISSING_FIELD % KEY_ACTION) if KEY_ACTION_ID in kwargs[KEY_ACTION] and len( kwargs[KEY_ACTION]) == 1: # We only got id of action, check if present in db try: action = predictor.get_action_class().objects.get( kwargs[KEY_ACTION][KEY_ACTION_ID]) except predictor.get_action_class().DoesNotExist: action = None # lets try to get action by native_id try: action = predictor.get_action_class().objects.get( native_id=kwargs[KEY_ACTION][KEY_ACTION_ID]) except predictor.get_action_class().DoesNotExist: pass # if there is an action lets use it if action is not None: attached_data = action.data or {} kwargs[KEY_ACTION].update(attached_data) # We didn't find it, nothing to do self.validate_action(kwargs[KEY_ACTION]) model = kwargs.get(KEY_MODEL, None) if KEY_REWARD not in kwargs: raise exc.InvalidParameterConfiguration(ERR_MSG_MISSING_FIELD % KEY_REWARD) score_start = datetime.utcnow() skip_training = parse_bool(kwargs.get('skip_training', True)) predictor.feedback(context, kwargs[KEY_ACTION], float(kwargs['reward']), model=model, skip_training=skip_training) latency = (datetime.utcnow() - score_start).total_seconds() return {'latency': latency}
def __resolve_parameters(self): try: result = {} for key, val in self.params.iteritems(): resolver = ATTR_RESOLVER_MAPPING.get(key, default_resolver) result[key] = resolver(val, self.platform, self.user) return result except Exception, e: raise exc.InvalidParameterConfiguration(e.message)
def _post(self, user, _id=None, *args, **kwargs): """ The standard POST request. Create a new instance and returns the value in the same form as a GET request would. """ for entry in self.required_fields: if entry not in kwargs: raise exc.InvalidParameterConfiguration( "Expected required field: '{}'".format(entry)) return self._format_single_doc( AuthTextClassifier.objects.create_by_user(user, *args, **kwargs))
def _fields2show(cls, **params): fields = params.pop('fields', None) if fields and not isinstance(fields, list): try: fields.split(',') except AttributeError: raise api_exc.InvalidParameterConfiguration("Parameter 'fields', when provided,\ must be a list or a comma separated string") return fields
def get(self, user, **kwargs): account_id = user.account.id if 'id' in kwargs or '_id' in kwargs: predictor_id = kwargs.get('id', kwargs.get('_id')) try: predictor = BasePredictor.objects.get(predictor_id) if predictor.account_id != account_id: raise exc.InvalidParameterConfiguration(ERR_MSG_NO_ACCESS % predictor_id) return dict(ok=True, item=self._format_doc(predictor)) except BasePredictor.DoesNotExist: raise exc.InvalidParameterConfiguration(ERR_MSG_NO_PREDICTOR % predictor_id) kwargs['account_id'] = account_id return dict(ok=True, list=[ self._format_doc(ag) for ag in BasePredictor.objects.find(**kwargs) ])
def __update_agent(self, user, agent_id, agent_data, AgentProfile, agent_profile_schema): if not agent_id: raise exc.InvalidParameterConfiguration("Expected required id for PUT requests: '{}'".format(agent_data)) agent_data, AgentProfile, agent_profile_schema = self.api_data_to_model_data(user, agent_data, profile_class=AgentProfile, profile_schema=agent_profile_schema) agent = AgentProfile.objects.get(_id=ObjectId(agent_id)) for field_name, field_value in agent_data.iteritems(): setattr(agent, field_name, field_value) agent.save() return agent, AgentProfile, agent_profile_schema
def _login_status(self, user, *args, **kwargs): batch_data = kwargs['batch_data'] agent_profile_schema = user.account.agent_profile._get() AgentProfile = agent_profile_schema.get_data_class() batch_data = json.loads(batch_data) if KEY_AGENT_LIST not in batch_data: raise exc.InvalidParameterConfiguration("Missing required parameter %s" % KEY_AGENT_LIST) if 'login_status' not in batch_data: raise exc.InvalidParameterConfiguration("Missing required parameter %s" % KEY_AGENT_LIST) login_status = batch_data['login_status'] counter = 0 for nid in batch_data[KEY_AGENT_LIST]: nid = str(nid) ap = AgentProfile.objects.get(native_id=nid) ap.attached_data['loginStatus'] = login_status ap.save() counter += 1 LOGGER.info("UPDATE LOGIN STATUS COUNT: %s" % counter) return dict(ok=True, n_updates=counter)
def dispatch_request(self, user, platform=None, name=None, **kwargs): """ Perform the RPC command by the platform and command name""" if not platform: raise exc.ResourceDoesNotExist("A 'platform' parameter is required") if not name: raise exc.ResourceDoesNotExist("A 'name' parameter is required") if platform not in TASK_MAPPING: raise exc.InvalidParameterConfiguration("Unsupported platform '{}'".format(platform)) if name not in TASK_MAPPING[platform]: raise exc.InvalidParameterConfiguration("Unsupported {} command '{}'".format(platform, name)) task = TASK_MAPPING[platform][name] data = _get_request_data() if 'media' in data and name in {'update_status', 'update_with_media', 'wall_post'}: data['media'] = make_temp_file(data['media']) #data = request.json executor = RPCTaskExecutor(task, platform, data) return dict(item=executor.execute())
def get_validated_context(self, predictor, kwargs): """ Validate context and convert its keys. Keys in context are case insensitive, all the keys in the context dictionary are converted into an upper case """ context_lower_mappings = dict() for ctx_key in predictor.context_feature_names: context_lower_mappings[ctx_key.lower()] = ctx_key if KEY_CONTEXT not in kwargs: raise exc.InvalidParameterConfiguration(ERR_MSG_MISSING_FIELD % KEY_CONTEXT) context = kwargs[KEY_CONTEXT] if not isinstance(context, dict): raise exc.InvalidParameterConfiguration(ERR_MSG_INVALID_JSON % (KEY_CONTEXT, context)) updated_context = {} for k, v in context.iteritems(): if k.lower() not in context_lower_mappings: continue updated_context[context_lower_mappings[k.lower()]] = v return updated_context
def get_tags(self, user, *args, **kwargs): if 'channel_id' not in kwargs: raise exc.InvalidParameterConfiguration("Expected required field 'channel_id'") try: channel = Channel.objects.get(kwargs['channel_id']) except Channel.DoesNotExist: return dict(ok=False, error="There is no channel with id=%s in the system." % kwargs['channel_id']) channels = [channel.id] if isinstance(channel, ServiceChannel): channels.extend([channel.inbound_channel.id, channel.outbound_channel.id]) return dict(ok=True, list=[dict(id=str(tag.id), display_name=str(tag.display_name)) for tag in EventTag.objects.find(channels__in=channels)])
def _delete(self, user, _id=None, *args, **kwargs): agent_profile_schema = user.account.agent_profile._get() AgentProfile = agent_profile_schema.get_data_class() agent_id = _id or kwargs.get('id', None) native_id = kwargs.get('native_id', None) if not agent_id and not native_id: raise exc.InvalidParameterConfiguration("Unsafe delete. Need to pass in id or native_id for deletion: '{}'".format(kwargs)) if agent_id: AgentProfile.objects.remove_by_user(user, _id=ObjectId(agent_id)) if native_id: AgentProfile.objects.remove_by_user(user, native_id=native_id) # Always 1 for now, this will be changed when batch comes into play return dict(removed_count=1)
def put(self, user, *args, **kwargs): if 'id' not in kwargs: raise exc.InvalidParameterConfiguration( "Need the id of the FAQ you want to update.") try: faq = FAQ.objects.get(kwargs['id']) except FAQ.DoesNotExist: raise exc.ResourceDoesNotExist("No FAQ with id=%s" % kwargs['id']) if 'question' in kwargs: faq.question = kwargs['question'] if 'answer' in kwargs: faq.answer = kwargs['answer'] faq.save() DbBasedSE1(faq.channel).compile_faqs() return dict(item=faq.to_dict())
def predict(self, user, *args, **kwargs): """ Tied to the predict endpoint. Accepts POST requests and does a prediction returning scores for each sample Sample requests: Generic request by some filter: curl http://staging.socialoptimizr.com/api/v2.0/classifiers/predict POST Parameters: :param token: <Required> - A valid user access token :param classifiers: <Required>: [list] a list of classifier ids indicating which classifiers to apply :param samples: <Required> - list of samples to classify. Each entry in the list is a dictionary. For text classification: {'text': <string>} The text to be classified. Output: A dictionary in the form { "latency": 98, "list": [ { "score": 0.5, "id": "5423f3b231eddd14ab477eea", "name": "ClassifierOne", "text": "This is some test post" }, { "score": 0.5, "id": "5423f3b231eddd14ab477eea", "name": "ClassifierOne", "text": "This is some laptop post" } ], "ok": true } """ if 'classifiers' not in kwargs: error_msg = "Missing required parameter 'classifiers'." error_desc = "You are missing a required parameter 'classifiers'. This should be a list " error_desc += "of Classifier ids that will be used to compute prediction scores on the sample list." raise exc.InvalidParameterConfiguration(error_msg, description=error_desc) if 'samples' not in kwargs: error_msg = "Missing required parameter 'samples'" error_desc = "You are missing a required parameter 'samples'. This should be a list of " error_desc += "text samples to classify. Each entry should be a simple dictionary {'text': <content>}" raise exc.InvalidParameterConfiguration(error_msg, description=error_desc) result = [] batch_start = datetime.utcnow() for classifier in AuthTextClassifier.objects( id__in=kwargs['classifiers']): score = classifier.batch_predict( [sample['text'] for sample in kwargs['samples']]) scores = [{ 'id': str(classifier.id), 'name': classifier.name, 'score': s['score'], 'text': s['text'] } for s in score] result.extend(scores) latency = (datetime.utcnow() - batch_start).total_seconds() return {'list': result, 'latency': latency}
def post(self, user, *args, **kwargs): """ :param channel: REQUIRED, the id of the channel we want to create a historic load for :param from_date: REQUIRED, the start date of the load, format is '%d.%m.%Y-%H:%M:%S' :param to_date: REQUIRED, the end date of the load, format is '%d.%m.%Y-%H:%M:%S' :returns A json response, format: {ok=<True|False>, error="", item=subscription.json} Sample response (Twitter): { "ok": true, "subscription": { "status": "created", "channel_id": "541aec9731eddd1fc4507853", "from_date": 1404388800, "to_date": 1405252800, "datasift_historic_id": null, "datasift_push_id": null, "id": "541aec9731eddd1fc4507857" } } Sample response (Facebook): { "ok": true, "subscription": { "status": "created", "finished": [], "actionable": [], "channel_id": "541aeccd31eddd1fec5b2059", "from_date": 1404388800, "to_date": 1405252800, "id": "541aeccd31eddd1fec5b205c" } } """ if 'channel' not in kwargs: exc_err = "Parameter 'channel' is required in order to create subscription." raise exc.InvalidParameterConfiguration(exc_err) channel = self.get_channel(user, kwargs) if 'from_date' not in kwargs: raise exc.InvalidParameterConfiguration( "Parameter 'from_date' required") if 'to_date' not in kwargs: raise exc.InvalidParameterConfiguration( "Parameter 'to_date' required") from_date = parse_datetime(kwargs['from_date']) to_date = parse_datetime(kwargs['to_date']) if from_date >= to_date: raise exc.InvalidParameterConfiguration( "'From' date must be less than 'To' date") type = kwargs.get('type', '') if type == 'event' and not channel.tracked_fb_event_ids: raise exc.InvalidParameterConfiguration( "No Events set, canceling recovery.") sc = get_service_channel(channel) if not isinstance(sc, FacebookServiceChannel): subscription_cls = HistoricalSubscriptionFactory.resolve(sc) if subscription_cls is None: raise exc.InvalidParameterConfiguration( "Could not infer a service channel for channel %s" % (channel.title + '<' + channel.__class__.__name__ + '>')) error = subscription_cls.validate_recovery_range( from_date, to_date) if error: raise exc.InvalidParameterConfiguration(error) if not kwargs.pop('force', False) and subscription_cls.objects.find_one( channel_id=sc.id, status__in=STATUS_ACTIVE): raise exc.ForbiddenOperation( "The recovery process is already in progress for this channel" ) subscription = subscription_cls.objects.create( created_by=user, channel_id=sc.id, from_date=from_date, to_date=to_date, status=SUBSCRIPTION_CREATED) if not get_var('APP_MODE') == 'test': subscription.process() return dict(ok=True, subscription=subscription.to_dict()) else: if isinstance(kwargs['from_date'], int) and isinstance( kwargs['to_date'], int): from_date_str = int(kwargs['from_date']) / 1000 to_date_str = int(kwargs['to_date']) / 1000 else: from_date_str = from_date.strftime("%s") to_date_str = to_date.strftime("%s") url = "%s?token=%s&channel=%s&since=%s&until=%s&type=%s" % \ (settings.FBOT_URL + '/json/restore', settings.FB_DEFAULT_TOKEN, sc.id, from_date_str, to_date_str, type) from solariat_bottle.tasks import async_requests async_requests.ignore('get', url, verify=False, timeout=None) return dict(ok=True)
def _validate_params(self, params): if self.required_fields: for field in self.required_fields: if field not in params: raise api_exc.InvalidParameterConfiguration("Expected required field: '{}'".format(field))