class ProgressLog(flask_restful.Resource): """Base class for a progress log resource. Attributes: client: datastore Client object used to communicate with Datastore. run_database: SystemRunDatabase object for querying and storing system runs using the client. log_database: ProgressLogDatabase object for querying and storing progress logs using the client. attempt_database: ImportAttemptDatabase object for querying and storing import attempts using the client. """ parser = reqparse.RequestParser() required_fields = [(_LOG.level, ), (_LOG.message, )] optional_fields = [(_LOG.time_logged, ), (_LOG.run_id, ), (_LOG.attempt_id, )] utils.add_fields(parser, required_fields, required=True) utils.add_fields(parser, optional_fields, required=False) def __init__(self, client=None, message_manager=None): """Constructs a ProgressLog. Args: client: datastore Client object used to communicate with Datastore. message_manager: LogMessageManager object used to store and retreive log messages. """ if not client: client = utils.create_datastore_client() self.client = client self.run_database = system_run_database.SystemRunDatabase(self.client) self.log_database = progress_log_database.ProgressLogDatabase( self.client, message_manager) self.attempt_database = import_attempt_database.ImportAttemptDatabase( self.client)
class ImportAttempt(base_resource.BaseResource): """Base class for an import attempt resource. Attributes: client: datastore Client object used to communicate with Datastore database: ImportAttemptDatabase object for querying and storing import attempts using the client """ parser = reqparse.RequestParser() # The parser looks for these fields in the request body. # The Content-Type of the request must be application/json. optional_fields = ((_MODEL.attempt_id, str), (_MODEL.run_id, str), (_MODEL.import_name,), (_MODEL.absolute_import_name,), (_MODEL.provenance_url,), (_MODEL.provenance_description,), (_MODEL.status,), (_MODEL.time_created,), (_MODEL.time_completed,), (_MODEL.logs, str, 'append'), (_MODEL.import_inputs, dict, 'append')) utils.add_fields(parser, optional_fields, required=False) def __init__(self, client=None): """Constructs an ImportAttempt.""" if not client: client = utils.create_datastore_client() self.client = client self.database = import_attempt_database.ImportAttemptDatabase( self.client)
def test_combined(self): """Tests that add_fields correctly adds both required and optional fields to a parser.""" parser = reqparse.RequestParser() required_fields = [('pr_number', int)] optional_fields = [('logs', dict, 'append')] utils.add_fields(parser, required_fields, required=True) utils.add_fields(parser, optional_fields, required=False) log_1 = {'level': 'info', 'message': 'ahhhhh'} log_2 = {'level': 'error', 'message': 'noooo'} with_pr_and_logs = {'pr_number': 1, 'logs': [log_1, log_2]} with main.FLASK_APP.test_request_context(json=with_pr_and_logs): args = parser.parse_args() self.assertEqual(with_pr_and_logs, args)
def test_required_fields(self): """Tests that add_fields correctly adds required fields to the parser.""" parser = reqparse.RequestParser() required_fields = [('pr_number', int)] utils.add_fields(parser, required_fields, required=True) with_pr = {'pr_number': 1, 'import_name': 'name'} with main.FLASK_APP.test_request_context(json=with_pr): args = parser.parse_args() self.assertEqual({'pr_number': 1}, args) without_pr = {'import_name': 'name'} with main.FLASK_APP.test_request_context(json=without_pr): with self.assertRaises(Exception) as context: parser.parse_args() self.assertEqual(400, context.exception.code)
def test_optional_fields(self): """Tests that add_fields correctly adds optional fields to the parser.""" parser = reqparse.RequestParser() optional_fields = [('attempt_id', str), ('import_name', str, 'store')] utils.add_fields(parser, optional_fields, required=False) with main.FLASK_APP.test_request_context(json={'pr_number': 1}): args = parser.parse_args() self.assertEqual({}, args) only_attempt_id = {'attempt_id': "0"} with main.FLASK_APP.test_request_context(json=only_attempt_id): args = parser.parse_args() self.assertEqual(only_attempt_id, args) both = {'attempt_id': '0', 'import_name': 'name'} with main.FLASK_APP.test_request_context(json=both): args = parser.parse_args() self.assertEqual(both, args)
class SystemRun(flask_restful.Resource): """Base class for a system run resource. Attributes: client: datastore Client object used to communicate with Datastore database: SystemRunDatabase object for querying and storing system runs using the client """ parser = reqparse.RequestParser() optional_fields = ((_MODEL.run_id, str), (_MODEL.repo_name,), (_MODEL.branch_name,), (_MODEL.pr_number, int), (_MODEL.commit_sha,), (_MODEL.time_created,), (_MODEL.time_completed,), (_MODEL.import_attempts, str, 'append'), (_MODEL.logs, str, 'append'), (_MODEL.status,)) utils.add_fields(parser, optional_fields, required=False) def __init__(self, client=None): """Constructs a SystemRun.""" if not client: client = utils.create_datastore_client() self.client = client self.database = system_run_database.SystemRunDatabase(self.client)
def test_dict(self): """Tests parsing dicts.""" parser = reqparse.RequestParser() utils.add_fields(parser, [('field', dict, 'append')], required=True) body = {'field': [{'abc': '1'}, {'def': '2', 'ghi': '3'}, {}]} with main.FLASK_APP.test_request_context(json=body): self.assertEqual(body, parser.parse_args()) # Empty list throws exception if field required parser = reqparse.RequestParser() utils.add_fields(parser, [('field', dict, 'append')], required=True) body = {'field': []} with self.assertRaises(exceptions.BadRequest): with main.FLASK_APP.test_request_context(json=body): parser.parse_args() # Empty list accepted if field not required parser = reqparse.RequestParser() utils.add_fields(parser, [('field', dict, 'append')], required=False) body = {'field': []} with main.FLASK_APP.test_request_context(json=body): self.assertEqual({}, parser.parse_args())
class SystemRunList(system_run.SystemRun): """API for querying a list of system runs based on some criteria and for creating new system runs. See SystemRun. """ _parser = system_run.SystemRun.parser.copy() utils.add_fields(_parser, (('limit', int), ('order', str, 'append')), required=False) def get(self): """Retrieves a list of system runs that pass the filter defined by the key-value mappings in the request body. The filter can only contain fields defined by SystemRun. This endpoint accepts two url arguments: limit and order. limit is an integer that specifies the maximum number of system runs returned and order is a list of field names to order the returned system runs by. Prepend "-" to a field name to sort it in descending order. The list can be specified by repeated keys. E.g., ?order=status&order=-time_created. Returns: A list of system runs each as a datastore Entity object if successful. Otherwise, (error message, error code), where the error message is a string and the error code is an int. """ args = None try: args = SystemRunList._parser.parse_args(strict=True) except exceptions.BadRequest as exc: return exc.description, exc.code order = args.pop('order', ()) limit = args.pop('limit', None) for field in args: if field not in system_run_model.FIELDS: return (f'Field {field} is not a valid field for a system run', http.HTTPStatus.BAD_REQUEST) return self.database.filter(args, order=order, limit=limit) def post(self): """Creates a new system run with the fields provided in the request body. Returns: The created system run as a datastore Entity object with run_id set. Otherwise, (error message, error code), where the error message is a string and the error code is an int. """ args = system_run.SystemRunByID.parser.parse_args() valid, err, code = validation.is_system_run_valid(args) if not valid: return err, code # Only the API can modify these fields args.pop('run_id', None) args.pop('import_attempts', None) args.pop('logs', None) system_run.set_system_run_default_values(args) with self.client.transaction(): run = self.database.get(make_new=True) run.update(args) return self.database.save(run)