Coverage for yaptide/routes/user_routes.py: 91%

65 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2024-11-22 07:31 +0000

1import logging 

2from enum import Enum 

3 

4from flask import request 

5from flask_restful import Resource 

6from marshmallow import Schema, fields 

7from sqlalchemy import asc, desc 

8 

9from yaptide.persistence.models import SimulationModel, UserModel 

10from yaptide.routes.utils.decorators import requires_auth 

11from yaptide.routes.utils.response_templates import (error_validation_response, yaptide_response) 

12from yaptide.persistence.db_methods import (delete_object_from_db, fetch_simulation_by_job_id) 

13from yaptide.utils.enums import EntityState 

14 

15DEFAULT_PAGE_SIZE = 6 # default number of simulations per page 

16DEFAULT_PAGE_IDX = 1 # default page index 

17 

18 

19class OrderType(Enum): 

20 """Order type""" 

21 

22 ASCEND = "ascend" 

23 DESCEND = "descend" 

24 

25 

26class OrderBy(Enum): 

27 """Order by column""" 

28 

29 START_TIME = "start_time" 

30 END_TIME = "end_time" 

31 

32 

33class UserSimulations(Resource): 

34 """Class responsible for returning user's simulations' basic infos""" 

35 

36 class GetAPIParametersSchema(Schema): 

37 """Class specifies Get API parameters""" 

38 

39 page_size = fields.Integer(load_default=DEFAULT_PAGE_SIZE) 

40 page_idx = fields.Integer(load_default=DEFAULT_PAGE_IDX) 

41 order_by = fields.String(load_default=OrderBy.START_TIME.value) 

42 order_type = fields.String(load_default=OrderType.DESCEND.value) 

43 

44 class DeleteAPIParametersSchema(Schema): 

45 """Schema for DELETE method parameters""" 

46 

47 job_id = fields.String(required=True) # job_id is mandatory for DELETE 

48 

49 @staticmethod 

50 @requires_auth() 

51 def get(user: UserModel): 

52 """Method returning simulations from the database""" 

53 schema = UserSimulations.GetAPIParametersSchema() 

54 params_dict: dict = schema.load(request.args) 

55 logging.info('User %s requested simulations with parameters: %s', user.username, params_dict) 

56 

57 # Query the database for the paginated results 

58 sorting = desc if params_dict['order_type'] == OrderType.DESCEND.value else asc 

59 query = SimulationModel.query.\ 

60 filter(SimulationModel.job_id != None).\ 

61 filter_by(user_id=user.id).\ 

62 order_by(sorting(params_dict['order_by'])) 

63 pagination = query.paginate(page=params_dict['page_idx'], per_page=params_dict['page_size'], error_out=False) 

64 simulations = pagination.items 

65 

66 result = { 

67 'simulations': [ 

68 { 

69 'title': simulation.title, 

70 'job_id': simulation.job_id, 

71 'start_time': simulation.start_time, 

72 # submission time, when user send the request to the backend - jobs may start much later than that 

73 'end_time': simulation.end_time, 

74 # end time, when the all jobs are finished and results are merged 

75 'metadata': { 

76 'platform': simulation.platform, 

77 'server': 'Yaptide', 

78 'input_type': simulation.input_type, 

79 'sim_type': simulation.sim_type 

80 } 

81 } for simulation in simulations 

82 ], 

83 'page_count': 

84 pagination.pages, 

85 'simulations_count': 

86 pagination.total, 

87 } 

88 return yaptide_response(message='User Simulations', code=200, content=result) 

89 

90 @staticmethod 

91 @requires_auth() 

92 def delete(user: UserModel): 

93 """Method deleting simulation from database""" 

94 schema = UserSimulations.DeleteAPIParametersSchema() 

95 errors: dict[str, list[str]] = schema.validate(request.args) 

96 if errors: 

97 return error_validation_response(content=errors) 

98 params_dict: dict = schema.load(request.args) 

99 

100 job_id = params_dict['job_id'] 

101 simulation = fetch_simulation_by_job_id(job_id) 

102 

103 if simulation is None: 

104 return yaptide_response(message=f'Simulation with job_id={job_id} do not exist', code=404) 

105 

106 if simulation.user_id != user.id: 

107 return yaptide_response(message='Unauthorized: You do not have permission to delete this simulation', 

108 code=401) 

109 

110 # Simulation has to be completed/cancelled before deleting it. 

111 if simulation.job_state in (EntityState.UNKNOWN.value, EntityState.PENDING.value, EntityState.RUNNING.value): 

112 return yaptide_response(message=f'''Simulation with job_id={job_id} is currently running. 

113 Please cancel simulation or wait for it to finish''', 

114 code=403) 

115 

116 delete_object_from_db(simulation) 

117 return yaptide_response(message=f'Simulation with job_id={job_id} successfully deleted from database', code=200) 

118 

119 

120class UserUpdate(Resource): 

121 """Class responsible for updating the user""" 

122 

123 @staticmethod 

124 @requires_auth() 

125 def post(user: UserModel): 

126 """Updates user with provided parameters""" 

127 json_data: dict = request.get_json(force=True) 

128 if not json_data: 

129 return error_validation_response() 

130 return yaptide_response(message=f'User {user.username} updated', code=202)