async def action_handler(service, action_type, payload, props, **kwds): # if the payload represents a new instance of `model` if action_type == get_crud_action('read', name or Model): # the props of the message message_props = {} # if there was a correlation id in the request if 'correlation_id' in props: # make sure it ends up in the reply message_props['correlation_id'] = props['correlation_id'] try: # resolve the query using the service schema resolved = service.schema.execute(payload) # create the string response response = json.dumps({ 'data': {key:value for key,value in resolved.data.items()}, 'errors': resolved.errors }) # publish the success event await service.event_broker.send( payload=response, action_type=change_action_status(action_type, success_status()), **message_props ) # if something goes wrong except Exception as err: # publish the error as an event await service.event_broker.send( payload=str(err), action_type=change_action_status(action_type, error_status()), **message_props )
def _create_linked_handler(self, model): # the related action type related_action_type = get_crud_action('delete', model, status=success_status()) # the action handler async def action_handler(action_type, payload, notify=True, **kwds): """ an action handler to remove related entries in the connection db. """ # if the action designates a successful delete of the model if action_type == related_action_type: # the id of the deleted model related_id = payload['id'] # the query for matching fields matching_records = getattr( self.model, model_service_name(model)) == related_id ids = [ model.id for model in self.model.filter(matching_records) ] # find the matching records self.model.delete().where(matching_records).execute() # if we are supposed to notify if notify: # notify of the related delete await self.event_broker.send(action_type=get_crud_action( 'delete', self.model, status=success_status()), payload=ids) # pass the action handler return action_handler
def _create_linked_handler(self, model): # the related action type related_action_type = get_crud_action('delete', model, status=success_status()) # the action handler async def action_handler(action_type, payload, notify=True, **kwds): """ an action handler to remove related entries in the connection db. """ # if the action designates a successful delete of the model if action_type == related_action_type: # the id of the deleted model related_id = payload['id'] # the query for matching fields matching_records = getattr(self.model, model_service_name(model)) == related_id ids = [model.id for model in self.model.filter(matching_records)] # find the matching records self.model.delete().where(matching_records).execute() # if we are supposed to notify if notify: # notify of the related delete await self.event_broker.send( action_type=get_crud_action('delete', self.model, status=success_status()), payload=ids ) # pass the action handler return action_handler
def summarize_crud_mutation(method, model, isAsync=False): """ This function provides the standard form for crud mutations. """ # create the approrpriate action type action_type = get_crud_action(method=method, model=model) # the name of the mutation name = crud_mutation_name(model=model, action=method) # a mapping of methods to input factories input_map = { 'create': create_mutation_inputs, 'update': update_mutation_inputs, 'delete': delete_mutation_inputs, } # a mappting of methods to output factories output_map = { 'create': create_mutation_outputs, 'update': update_mutation_outputs, 'delete': delete_mutation_outputs, } # the inputs for the mutation inputs = input_map[method](model) # the mutation outputs outputs = output_map[method](model) # return the appropriate summary return summarize_mutation(mutation_name=name, event=action_type, isAsync=isAsync, inputs=inputs, outputs=outputs)
async def action_handler(service, action_type, payload, props, notify=True, **kwds): # if the payload represents a new instance of `Model` if action_type == get_crud_action('create', name or Model): # print('handling create for ' + name or Model) try: # the props of the message message_props = {} # if there was a correlation id in the request if 'correlation_id' in props: # make sure it ends up in the reply message_props['correlation_id'] = props['correlation_id'] # for each required field for requirement in Model.required_fields(): # save the name of the field field_name = requirement.name # ensure the value is in the payload # TODO: check all required fields rather than failing on the first if not field_name in payload and field_name != 'id': # yell loudly raise ValueError( "Required field not found in payload: %s" % field_name) # create a new model new_model = Model(**payload) # save the new model instance new_model.save() # if we need to tell someone about what happened if notify: # publish the scucess event await service.event_broker.send( payload=ModelSerializer().serialize(new_model), action_type=change_action_status( action_type, success_status()), **message_props) # if something goes wrong except Exception as err: # if we need to tell someone about what happened if notify: # publish the error as an event await service.event_broker.send( payload=str(err), action_type=change_action_status( action_type, error_status()), **message_props) # otherwise we aren't supposed to notify else: # raise the exception normally raise err
async def action_handler(service, action_type, payload, props, notify=True, **kwds): # if the payload represents a new instance of `Model` if action_type == get_crud_action('update', name or Model): try: # the props of the message message_props = {} # if there was a correlation id in the request if 'correlation_id' in props: # make sure it ends up in the reply message_props['correlation_id'] = props['correlation_id'] # grab the nam eof the primary key for the model pk_field = Model.primary_key() # make sure there is a primary key to id the model if not pk_field.name in payload: # yell loudly raise ValueError("Must specify the pk of the model when updating") # grab the matching model model = Model.select().where(pk_field == payload[pk_field.name]).get() # remove the key from the payload payload.pop(pk_field.name, None) # for every key,value pair for key, value in payload.items(): # TODO: add protection for certain fields from being # changed by the api setattr(model, key, value) # save the updates model.save() # if we need to tell someone about what happened if notify: # publish the scucess event await service.event_broker.send( payload=ModelSerializer().serialize(model), action_type=change_action_status(action_type, success_status()), **message_props ) # if something goes wrong except Exception as err: # if we need to tell someone about what happened if notify: # publish the error as an event await service.event_broker.send( payload=str(err), action_type=change_action_status(action_type, error_status()), **message_props ) # otherwise we aren't supposed to notify else: # raise the exception normally raise err
def test_can_change_action_types(self): # try to creat a crud action for a model action_type = get_crud_action(method='create', model=self.model) # try to change the action success_action = change_action_status(action_type, 'success') # make sure the new action type is in the result assert success_action == 'create.testModel.success', ( "New action type did not have the correct form.")
def test_can_change_action_types(self): # try to creat a crud action for a model action_type = get_crud_action(method='create', model=self.model) # try to change the action success_action = change_action_status(action_type, 'success') # make sure the new action type is in the result assert success_action == 'create.testModel.success', ( "New action type did not have the correct form." )
def test_can_generate_crud_action_types(self): # try to creat a crud action for a model action_type = get_crud_action( method='create', model=self.model ) assert action_type == 'create.testModel.pending', ( "New action type did not have the correct form." )
def _verify_crud_mutation(self, model, action): # create the mutation summarized = summarize_crud_mutation(model=model, method=action) # make sure the name matches the convention assert summarized['name'] == crud_mutation_name(model=model, action=action), ( "Summarized %s mutation did not have the right name." % action ) # make sure the event is what we expect assert summarized['event'] == get_crud_action(model=model, method=action), ( "Summarized %s mutation did not have the right event type." % action )
async def action_handler(service, action_type, payload, props, notify=True, **kwds): # if the payload represents a new instance of `Model` if action_type == get_crud_action('create', name or Model): # print('handling create for ' + name or Model) try: # the props of the message message_props = {} # if there was a correlation id in the request if 'correlation_id' in props: # make sure it ends up in the reply message_props['correlation_id'] = props['correlation_id'] # for each required field for requirement in Model.required_fields(): # save the name of the field field_name = requirement.name # ensure the value is in the payload # TODO: check all required fields rather than failing on the first if not field_name in payload and field_name != 'id': # yell loudly raise ValueError( "Required field not found in payload: %s" %field_name ) # create a new model new_model = Model(**payload) # save the new model instance new_model.save() # if we need to tell someone about what happened if notify: # publish the scucess event await service.event_broker.send( payload=ModelSerializer().serialize(new_model), action_type=change_action_status(action_type, success_status()), **message_props ) # if something goes wrong except Exception as err: # if we need to tell someone about what happened if notify: # publish the error as an event await service.event_broker.send( payload=str(err), action_type=change_action_status(action_type, error_status()), **message_props ) # otherwise we aren't supposed to notify else: # raise the exception normally raise err
def _verify_crud_mutation(self, model, action): # create the mutation summarized = summarize_crud_mutation(model=model, method=action) # make sure the name matches the convention assert summarized['name'] == crud_mutation_name( model=model, action=action), ( "Summarized %s mutation did not have the right name." % action) # make sure the event is what we expect assert summarized['event'] == get_crud_action( model=model, method=action), ( "Summarized %s mutation did not have the right event type." % action)
async def action_handler(service, action_type, payload, props, notify=True, **kwds): # if the payload represents a new instance of `model` if action_type == get_crud_action('delete', name or Model): try: # the props of the message message_props = {} # if there was a correlation id in the request if 'correlation_id' in props: # make sure it ends up in the reply message_props['correlation_id'] = props['correlation_id'] # the id in the payload representing the record to delete record_id = payload['id'] if 'id' in payload else payload['pk'] # get the model matching the payload try: model_query = Model.select().where( Model.primary_key() == record_id) except KeyError: raise RuntimeError( "Could not find appropriate id to remove service record." ) # remove the model instance model_query.get().delete_instance() # if we need to tell someone about what happened if notify: # publish the success event await service.event_broker.send( payload='{"status":"ok"}', action_type=change_action_status( action_type, success_status()), **message_props) # if something goes wrong except Exception as err: # if we need to tell someone about what happened if notify: # publish the error as an event await service.event_broker.send( payload=str(err), action_type=change_action_status( action_type, error_status()), **message_props) # otherwise we aren't supposed to notify else: # raise the exception normally raise err
async def _create_remote_user(self, **payload): """ This method creates a service record in the remote user service with the given email. Args: uid (str): the user identifier to create Returns: (dict): a summary of the user that was created """ # the action for reading user entries read_action = get_crud_action(method='create', model='user') # see if there is a matching user user_data = await self.event_broker.ask(action_type=read_action, payload=payload) # treat the reply like a json object return json.loads(user_data)
async def connection_resolver(self, connection_name, object): try: # grab the recorded data for this connection expected = [ conn for conn in self._external_service_data['connections']\ if conn['name'] == connection_name][0] # if there is no connection data yet except AttributeError: raise ValueError("No objects are registered with this schema yet.") # if we dont recognize the model that was requested except IndexError: raise ValueError("Cannot query for {} on {}.".format( connection_name, object['name'])) # the target of the connection to_service = expected['connection']['to']['service'] # ask for only the entries connected to the object filters = {object['name']: object['pk']} # the field of the connection is the model name fields = [to_service] # the query for model records query = query_for_model(fields, **filters).replace("'", '"') # the action type for the question action_type = get_crud_action('read', connection_name) # get the service name for the connection response = json.loads(await self.event_broker.ask(action_type=action_type, payload=query)) if 'errors' in response and response['errors']: # return an empty response raise ValueError(','.join(response['errors'])) # grab the ids from the response ids = [ int(entry[to_service]) for entry in response['data']['all_models'] ] # the question for connected nodes return ids, to_service
async def _get_matching_user(self, fields=[], **filters): # the action type for a remote query read_action = get_crud_action(method='read', model='user') # the fields of the user to ask for user_fields = ['pk'] + fields # the query for matching entries payload = """ query { %s(%s) { %s } } """ % (root_query(), arg_string_from_dict(filters), '\n'.join(user_fields)) # perform the query and return the result return json.loads(await self.event_broker.ask(action_type=read_action, payload=payload))
async def _create_remote_user(self, **payload): """ This method creates a service record in the remote user service with the given email. Args: uid (str): the user identifier to create Returns: (dict): a summary of the user that was created """ # the action for reading user entries read_action = get_crud_action(method='create', model='user') # see if there is a matching user user_data = await self.event_broker.ask( action_type=read_action, payload=payload ) # treat the reply like a json object return json.loads(user_data)
async def connection_resolver(self, connection_name, object): try: # grab the recorded data for this connection expected = [ conn for conn in self._external_service_data['connections']\ if conn['name'] == connection_name][0] # if there is no connection data yet except AttributeError: raise ValueError("No objects are registered with this schema yet.") # if we dont recognize the model that was requested except IndexError: raise ValueError("Cannot query for {} on {}.".format(connection_name, object['name'])) # the target of the connection to_service = expected['connection']['to']['service'] # ask for only the entries connected to the object filters = {object['name']: object['pk']} # the field of the connection is the model name fields = [to_service] # the query for model records query = query_for_model(fields, **filters).replace("'", '"') # the action type for the question action_type = get_crud_action('read', connection_name) # get the service name for the connection response = json.loads(await self.event_broker.ask( action_type=action_type, payload=query )) if 'errors' in response and response['errors']: # return an empty response raise ValueError(','.join(response['errors'])) # grab the ids from the response ids = [int(entry[to_service]) for entry in response['data']['all_models']] # the question for connected nodes return ids, to_service
async def action_handler(service, action_type, payload, props, notify=True, **kwds): # if the payload represents a new instance of `model` if action_type == get_crud_action('delete', name or Model): try: # the props of the message message_props = {} # if there was a correlation id in the request if 'correlation_id' in props: # make sure it ends up in the reply message_props['correlation_id'] = props['correlation_id'] # the id in the payload representing the record to delete record_id = payload['id'] if 'id' in payload else payload['pk'] # get the model matching the payload try: model_query = Model.select().where(Model.primary_key() == record_id) except KeyError: raise RuntimeError("Could not find appropriate id to remove service record.") # remove the model instance model_query.get().delete_instance() # if we need to tell someone about what happened if notify: # publish the success event await service.event_broker.send( payload='{"status":"ok"}', action_type=change_action_status(action_type, success_status()), **message_props ) # if something goes wrong except Exception as err: # if we need to tell someone about what happened if notify: # publish the error as an event await service.event_broker.send( payload=str(err), action_type=change_action_status(action_type, error_status()), **message_props ) # otherwise we aren't supposed to notify else: # raise the exception normally raise err
async def _get_matching_user(self, fields=[], **filters): # the action type for a remote query read_action = get_crud_action(method='read', model='user') # the fields of the user to ask for user_fields = ['pk'] + fields # the query for matching entries payload = """ query { %s(%s) { %s } } """ % (root_query(), arg_string_from_dict(filters), '\n'.join(user_fields)) # perform the query and return the result return json.loads(await self.event_broker.ask( action_type=read_action, payload=payload ))
def summarize_crud_mutation(method, model, isAsync=False): """ This function provides the standard form for crud mutations. """ # create the approrpriate action type action_type = get_crud_action(method=method, model=model) # the name of the mutation name = crud_mutation_name(model=model, action=method) # a mapping of methods to input factories input_map = {"create": create_mutation_inputs, "update": update_mutation_inputs, "delete": delete_mutation_inputs} # a mappting of methods to output factories output_map = { "create": create_mutation_outputs, "update": update_mutation_outputs, "delete": delete_mutation_outputs, } # the inputs for the mutation inputs = input_map[method](model) # the mutation outputs outputs = output_map[method](model) # return the appropriate summary return summarize_mutation(mutation_name=name, event=action_type, isAsync=isAsync, inputs=inputs, outputs=outputs)
def test_can_generate_crud_action_types(self): # try to creat a crud action for a model action_type = get_crud_action(method='create', model=self.model) assert action_type == 'create.testModel.pending', ( "New action type did not have the correct form.")
async def object_resolver(self, object_name, fields, obey_auth=False, current_user=None, **filters): """ This function resolves a given object in the remote backend services """ try: # check if an object with that name has been registered registered = [model for model in self._external_service_data['models'] \ if model['name']==object_name][0] # if there is no connection data yet except AttributeError: raise ValueError("No objects are registered with this schema yet.") # if we dont recognize the model that was requested except IndexError: raise ValueError("Cannot query for object {} on this service.".format(object_name)) # the valid fields for this object valid_fields = [field['name'] for field in registered['fields']] # figure out if any invalid fields were requested invalid_fields = [field for field in fields if field not in valid_fields] try: # make sure we never treat pk as invalid invalid_fields.remove('pk') # if they weren't asking for pk as a field except ValueError: pass # if there were if invalid_fields: # yell loudly raise ValueError("Cannot query for fields {!r} on {}".format( invalid_fields, registered['name'] )) # make sure we include the id in the request fields.append('pk') # the query for model records query = query_for_model(fields, **filters) # the action type for the question action_type = get_crud_action('read', object_name) # query the appropriate stream for the information response = await self.event_broker.ask( action_type=action_type, payload=query ) # treat the reply like a json object response_data = json.loads(response) # if something went wrong if 'errors' in response_data and response_data['errors']: # return an empty response raise ValueError(','.join(response_data['errors'])) # grab the valid list of matches result = response_data['data'][root_query()] # grab the auth handler for the object auth_criteria = self.auth_criteria.get(object_name) # if we care about auth requirements and there is one for this object if obey_auth and auth_criteria: # build a second list of authorized entries authorized_results = [] # for each query result for query_result in result: # create a graph entity for the model graph_entity = GraphEntity(self, model_type=object_name, id=query_result['pk']) # if the auth handler passes if await auth_criteria(model=graph_entity, user_id=current_user): # add the result to the final list authorized_results.append(query_result) # overwrite the query result result = authorized_results # apply the auth handler to the result return result
async def object_resolver(self, object_name, fields, obey_auth=False, current_user=None, **filters): """ This function resolves a given object in the remote backend services """ try: # check if an object with that name has been registered registered = [model for model in self._external_service_data['models'] \ if model['name']==object_name][0] # if there is no connection data yet except AttributeError: raise ValueError("No objects are registered with this schema yet.") # if we dont recognize the model that was requested except IndexError: raise ValueError( "Cannot query for object {} on this service.".format( object_name)) # the valid fields for this object valid_fields = [field['name'] for field in registered['fields']] # figure out if any invalid fields were requested invalid_fields = [ field for field in fields if field not in valid_fields ] try: # make sure we never treat pk as invalid invalid_fields.remove('pk') # if they weren't asking for pk as a field except ValueError: pass # if there were if invalid_fields: # yell loudly raise ValueError("Cannot query for fields {!r} on {}".format( invalid_fields, registered['name'])) # make sure we include the id in the request fields.append('pk') # the query for model records query = query_for_model(fields, **filters) # the action type for the question action_type = get_crud_action('read', object_name) # query the appropriate stream for the information response = await self.event_broker.ask(action_type=action_type, payload=query) # treat the reply like a json object response_data = json.loads(response) # if something went wrong if 'errors' in response_data and response_data['errors']: # return an empty response raise ValueError(','.join(response_data['errors'])) # grab the valid list of matches result = response_data['data'][root_query()] # grab the auth handler for the object auth_criteria = self.auth_criteria.get(object_name) # if we care about auth requirements and there is one for this object if obey_auth and auth_criteria: # build a second list of authorized entries authorized_results = [] # for each query result for query_result in result: # create a graph entity for the model graph_entity = GraphEntity(self, model_type=object_name, id=query_result['pk']) # if the auth handler passes if await auth_criteria(model=graph_entity, user_id=current_user): # add the result to the final list authorized_results.append(query_result) # overwrite the query result result = authorized_results # apply the auth handler to the result return result
async def action_handler(service, action_type, payload, props, notify=True, **kwds): # if the payload represents a new instance of `Model` if action_type == get_crud_action('update', name or Model): try: # the props of the message message_props = {} # if there was a correlation id in the request if 'correlation_id' in props: # make sure it ends up in the reply message_props['correlation_id'] = props['correlation_id'] # grab the nam eof the primary key for the model pk_field = Model.primary_key() # make sure there is a primary key to id the model if not pk_field.name in payload: # yell loudly raise ValueError( "Must specify the pk of the model when updating") # grab the matching model model = Model.select().where( pk_field == payload[pk_field.name]).get() # remove the key from the payload payload.pop(pk_field.name, None) # for every key,value pair for key, value in payload.items(): # TODO: add protection for certain fields from being # changed by the api setattr(model, key, value) # save the updates model.save() # if we need to tell someone about what happened if notify: # publish the scucess event await service.event_broker.send( payload=ModelSerializer().serialize(model), action_type=change_action_status( action_type, success_status()), **message_props) # if something goes wrong except Exception as err: # if we need to tell someone about what happened if notify: # publish the error as an event await service.event_broker.send( payload=str(err), action_type=change_action_status( action_type, error_status()), **message_props) # otherwise we aren't supposed to notify else: # raise the exception normally raise err