def check_consistency_warehouses(self): warehouses = self.instance.get_warehouses() flows = TupList(self.solution.data["flows"]) flow_in = ( flows.vfilter(lambda v: v["destination"] in warehouses) .take(["destination", "day", "product", "flow"]) .to_dict(result_col=3, is_list=True) .vapply(lambda v: sum(v)) ) flow_out = ( flows.vfilter(lambda v: v["origin"] in warehouses) .take(["origin", "day", "product", "flow"]) .to_dict(result_col=3, is_list=True) .vapply(lambda v: sum(v)) ) return ( flow_out.kvapply(lambda k, v: v - flow_in.get(k, 0)) .update(flow_in.kvapply(lambda k, v: flow_out.get(k, 0) - v)) .vfilter(lambda v: v != 0) )
def check_consistency_suppliers(self): suppliers = self.instance.get_suppliers() clients = self.instance.get_clients() flows = TupList(self.solution.data["flows"]) sent = ( flows.vfilter(lambda v: v["origin"] in suppliers) .take(["day", "product", "flow"]) .to_dict(result_col=2, is_list=True) .vapply(lambda v: sum(v)) ) received = ( flows.vfilter(lambda v: v["destination"] in clients) .take(["day", "product", "flow"]) .to_dict(result_col=2, is_list=True) .vapply(lambda v: sum(v)) ) return ( received.kvapply(lambda k, v: v - sent.get(k, 0)) .update(sent.kvapply(lambda k, v: received.get(k, 0) - v)) .vfilter(lambda v: v != 0) )
class Instance(InstanceCore): schema = load_json( os.path.join(os.path.dirname(__file__), "../schemas/instance.json") ) def __init__(self, data: dict): super().__init__(data) # Stores a list of the starting date of each week ordered self.weeks = TupList() # First object stores a list of the dates ordered, # the second the properties for each date. self.dates = TupList() self.dates_properties = SuperDict() # First object stores a list of the time slots ordered, # the second the properties for each one. self.time_slots = TupList() self.time_slots_properties = SuperDict() self.cache_properties() @classmethod def from_dict(cls, data: dict) -> "Instance": tables = ["employees", "shifts", "contracts"] data_p = {el: {v["id"]: v for v in data[el]} for el in tables} data_p["demand"] = {(el["day"], el["hour"]): el for el in data["demand"]} data_p["parameters"] = pickle.loads(pickle.dumps(data["parameters"], -1)) if data.get("skill_demand"): data_p["skill_demand"] = { (el["day"], el["hour"], el["id_skill"]): el for el in data["skill_demand"] } else: data_p["skill_demand"] = {} if data.get("skills"): data_p["skills"] = { el["id"]: el for el in data["skills"] } else: data_p["skills"] = {} if data.get("skills_employees"): data_p["skills_employees"] = TupList(data["skills_employees"]).to_dict( result_col=["id_employee"], is_list=True, indices=["id_skill"] ) else: data_p["skills_employees"] = TupList() return cls(data_p) def to_dict(self) -> Dict: tables = ["employees", "shifts", "contracts", "demand", "skill_demand", "skills"] data_p = {el: self.data[el].values_l() for el in tables} data_p["parameters"] = self.data["parameters"] data_p["skills_employees"] = [ dict(id_employee=id_employee, id_skill=id_skill) for id_skill in self.data["skills_employees"] for id_employee in self.data["skills_employees"][id_skill] ] return pickle.loads(pickle.dumps(data_p, -1)) def cache_properties(self): """Caches the list of weeks, dates and time slots and its associated properties""" self.weeks = self._get_weeks() self.dates = self._get_dates() self.dates_properties = self._get_dates_properties() self.time_slots = self._get_time_slots() self.time_slots_properties = self._get_time_slots_properties() def _get_weeks(self) -> TupList: """ Returns a TupList with the starting date of each week in date time format For example: [datetime(2021, 9, 6, 0, 0, 0), datetime(2021, 9, 13, 0, 0, 0), ...] """ return TupList( [ get_one_date(self._get_start_date(), weeks=i) for i in range(0, self._get_horizon()) ] ).sorted() def _get_dates(self) -> TupList: """ Returns a TupList with the dates of the whole horizon in datetime format For example: [datetime(2021, 9, 6, 0, 0, 0), datetime(2021, 9, 7, 0, 0, 0), ...] """ return TupList( [ get_one_date(self._get_start_date(), pos, d) for d in range(0, self._get_opening_days()) for pos, value in enumerate(self.weeks) ] ).sorted() def _get_dates_properties(self) -> SuperDict: """ Returns a SuperDict with dates as key and its dict properties as a value For example: {datetime(2021, 9, 6, 0, 0, 0): {"string": "2021-09-06", "week": 36}, ...} """ return SuperDict( { date: { "string": get_date_string_from_ts(date), "week": get_week_from_ts(date), } for date in self.dates } ) def _get_date_string_from_date(self, date): """Returns the date string of a given date""" return self.dates_properties[date]["string"] def _get_week_from_date(self, date): """Returns the week number of a given date""" return self.dates_properties[date]["week"] def _get_time_slots(self) -> TupList: """ Returns a TupList with the time slots of the whole horizon in datetime format For example: [datetime(2021, 9, 6, 7, 0, 0), datetime(2021, 9, 6, 8, 0, 0), ...] """ nb_hours = self._get_ending_hour() - self._get_starting_hour() nb_slots = int(self._hour_to_slot(nb_hours)) def date_hour_ts(d, s): return get_one_date_time(d, self._get_minutes(s)) return TupList( [date_hour_ts(date, s) for date in self.dates for s in range(nb_slots)] ).sorted() def _get_time_slots_properties(self) -> SuperDict: """ Returns a SuperDict with the time slots as key and their properties dict as a value For example: {datetime(2021, 9, 6, 7, 0, 0): {"string": "2021-09-06T07:00", "date": "2021-09-06", "week": 36, "hour": 7.0}, ...} """ return SuperDict( { ts: { "string": get_time_slot_string(ts), "date": get_date_string_from_ts(ts), "week": get_week_from_ts(ts), "hour": get_hour_from_date_time(ts), } for ts in self.time_slots } ) def _get_time_slot_string(self, ts): """Returns the time slot string of a given time slot""" return self.time_slots_properties[ts]["string"] def _get_date_string_from_ts(self, ts): """Returns the date string of a given time slot""" return self.time_slots_properties[ts]["date"] def _get_week_from_ts(self, ts): """Returns the week number of a given time slot""" return self.time_slots_properties[ts]["week"] def _get_hour_from_ts(self, ts): """Returns the hour of a given time slot""" return self.time_slots_properties[ts]["hour"] def _get_property(self, key, prop) -> SuperDict: """Returns a SuperDict with the key of a given 'table' and the prop as value""" return self.data[key].get_property(prop) def _get_employees(self, prop) -> SuperDict: """Returns a SuperDict with the employee id as key and the prop as value""" return self._get_property("employees", prop) def _get_contracts(self, prop) -> SuperDict: """Returns a SuperDict with the contract id as key and the prop as value""" return self._get_property("contracts", prop) def _get_shifts(self, prop) -> SuperDict: """Returns a SuperDict with the shift id as key and the prop as value""" return self._get_property("shifts", prop) def _get_horizon(self) -> int: """Returns the value of the horizon parameter""" return self.data["parameters"]["horizon"] def _get_start_date(self) -> datetime: """Returns the datetime object of the starting date""" return get_date_from_string(self.data["parameters"]["starting_date"]) def _get_end_date(self) -> datetime: """ Returns the last working day based on the starting day, the horizon in weeks and the number of days worked It assumes that the working days start at the end of the week. For example: 5 opening days, 2 week horizon and starting date of 2021-09-06 would result in 2021-09-17 being the end date. """ days = -(7 - self._get_opening_days()) - 1 return get_one_date( self._get_start_date(), weeks=self._get_horizon(), days=days ) def _get_skills(self) -> TupList: """Returns a TupList containing the id of the skills""" return self.data["skills"].keys_tl() def get_employees_by_skill(self, id_skill) -> TupList: """Returns a TupList with the employees that have the given skill""" return self.data["skills_employees"][id_skill] def _get_opening_days(self) -> int: """Returns the number of days that have to be worked each week""" return self.data["parameters"]["opening_days"] def _get_starting_hour(self) -> float: """Returns the first hour of the day that has to be worked.""" return self.data["parameters"]["starting_hour"] def _get_ending_hour(self) -> float: """Returns the last hour of the day that has to be worked.""" return self.data["parameters"]["ending_hour"] def _get_min_resting_hours(self) -> int: """Returns the number of resting hours that the employees have to have between working days""" return self.data["parameters"]["min_resting_hours"] def _get_slot_length(self) -> float: """Returns the length of a time slot in minutes""" return self.data["parameters"]["slot_length"] def slot_to_hour(self, slots) -> int: """Converts from slots to hours""" return int(slots * self._get_slot_length() / 60) def _hour_to_slot(self, hours) -> int: """Converts from a hours to slots""" return int(hours * 60 / self._get_slot_length()) def _get_minutes(self, s) -> float: """Method to get the number of minutes from the start of the day given a slot""" return self._get_starting_hour() * 60 + s * self._get_slot_length() def _get_employees_contracts(self) -> SuperDict[Tuple[int, int], int]: """ Returns a SuperDict with the week and employee tuple as key and the contract id as value Contracts are supposed to start on Monday and end on Sunday For example: {(36, 1): 10, ...} """ default_date = get_date_string_from_ts(self._get_end_date()) contract_start = self._get_contracts("start_contract") contract_end = self._get_contracts("end_contract").vapply( lambda v: v or default_date ) return SuperDict( { (self._get_week_from_date(week), e): c for week in self.weeks for c, e in self._get_contracts("id_employee").items() if contract_start[c] <= self._get_date_string_from_date(week) <= contract_end[c] } ) def _get_employees_contract_hours(self) -> SuperDict[Tuple[int, int], int]: """ Returns a SuperDict with the week and employee tuple as key and the weekly hours of the contract as the value Contracts are supposed to start on Monday and end on Sunday For example: {(36, 1): 40, ...} """ contract_hours = self._get_contracts("weekly_hours") return self._get_employees_contracts().vapply( lambda c: self._hour_to_slot(contract_hours[c]) ) def _get_employees_contract_days(self) -> SuperDict[Tuple[int, int], int]: """ Returns a SuperDict with the week and employee tuple as key and the max days worked weekly of the contract as the value Contracts are supposed to start on Monday and end on Sunday For example: {(36, 1): 5, ...} """ contract_days = self._get_contracts("days_worked") return self._get_employees_contracts().vapply(lambda c: contract_days[c]) def _get_employees_contract_shift(self) -> SuperDict[Tuple[int, int], int]: """ Returns a SuperDict with the week and employee tuple as key and the shift id as value Contracts are supposed to start on Monday and end on Sunday For example: {(36, 1): 1, ...} """ contract_shift = self._get_contracts("id_shift") return self._get_employees_contracts().vapply(lambda c: contract_shift[c]) def _get_employees_contract_starting_hour(self) -> SuperDict: """ Returns a SuperDict with the week and employee tuple as key and the shift starting hour Contracts are supposed to start on Monday and end on Sunday For example: {(36, 1): 7, ...} """ start = self._get_shifts("start") return self._get_employees_contract_shift().vapply(lambda s: start[s]) def _get_employees_contract_ending_hour(self) -> SuperDict[Tuple[int, int], float]: """ Returns a SuperDict with the week and employee tuple as key and the shift ending hour Contracts are supposed to start on Monday and end on Sunday For example: {(36, 1): 21, ...} """ end = self._get_shifts("end") return self._get_employees_contract_shift().vapply(lambda s: end[s]) def _get_employee_time_slots_week(self) -> TupList: """ Returns a TupList with the combinations of employees, weeks, dates and time slots in which the employees can work. For example: [("2021-09-06T07:00", 36, "2021-09-06", 1), ("2021-09-06T08:00", 36, "2021-09-06", 1), ...] """ start = self._get_employees_contract_starting_hour() end = self._get_employees_contract_ending_hour() return TupList( (self._get_time_slot_string(ts), w, self._get_date_string_from_ts(ts), e) for ts in self.time_slots for (w, e) in start if self._get_week_from_ts(ts) == w and start[w, e] <= self._get_hour_from_ts(ts) < end[w, e] ) def get_employees_time_slots_week(self) -> SuperDict: """ Returns a SuperDict with the week and employee tuple as key and a list of time slots as value For example: {(36, 1): ["2021-09-06T07:00", "2021-09-06T08:00", ...], ...} """ return self._get_employee_time_slots_week().take([0, 1, 3]).to_dict(0) def get_employees_time_slots_day(self) -> SuperDict: """ Returns a SuperDict with the date and employee tuple as key and a list of time slots as value For example: {("2021-09-06", 1): ["2021-09-06T07:00", "2021-09-06T08:00", ...], ...} """ return self._get_employee_time_slots_week().take([0, 2, 3]).to_dict(0) def get_consecutive_time_slots_employee(self) -> TupList: """ Returns a TupList with a time slot, the nex time slot in the same day and an employee according to the employee availability For example: [("2021-09-06T07:00", "2021-09-06T08:00", 1), ...] """ return TupList( [ (ts, ts2, e) for (d, e), _time_slots in self.get_employees_time_slots_day().items() for ts, ts2 in zip(_time_slots, _time_slots[1:]) ] ) def get_opening_time_slots_set(self) -> set: """ Returns a TupList with the time slots strings For example: ["2021-09-06T07:00", "2021-09-06T08:00", ...] """ return self.time_slots.vapply(lambda v: self._get_time_slot_string(v)).to_set() def get_employees_ts_availability(self) -> TupList[Tuple[str, int]]: """ Returns a TupList with the combinations of employees and time slots in which the employees can work. For example: [("2021-09-06T07:00", 1), ("2021-09-06T07:00", 2), ...] """ return self._get_employee_time_slots_week().take([0, 3]) def get_max_working_slots_week(self) -> SuperDict[Tuple[int, int], int]: """ Returns a SuperDict with the week and employee tuple as key and the weekly hours of the contract as the value Contracts are supposed to start on Monday and end on Sunday For example: {(36, 1): 40, ...} """ return self._get_employees_contract_hours() def get_max_working_slots_day(self) -> SuperDict[Tuple[str, int], float]: """ Returns a SuperDict with the dates and employees tuple as a key and the maximum slots that can be worked on a day as value For example: {("2021-09-06", 1): 9, ("2021-09-06", 2): 9, ("2021-09-06", 3): 8, ...} """ # TODO: set up a hard limit from the parameters of the instance, set up as optional, # if there is value it should be that one, if not the current calculation return SuperDict( { (self._get_date_string_from_date(d), e): hours / self._get_employees_contract_days()[w, e] + 1 for d in self.dates for (w, e), hours in self._get_employees_contract_hours().items() if self._get_week_from_date(d) == w } ) def get_min_working_slots_day(self) -> SuperDict[Tuple[str, int], int]: """ Returns a SuperDict with the dates and employees tuple as a key and the minimum amount of slots that need to be worked each day For example: {("2021-09-06", 1): 4, ("2021-09-06", 2): 4, ("2021-09-06", 3): 4, ...} """ return SuperDict( { (self._get_date_string_from_date(d), e): self._hour_to_slot( self.data["parameters"]["min_working_hours"] ) for d in self.dates for e in self._get_employees("id") } ) def get_first_time_slot_day_employee(self) -> SuperDict[Tuple[str, int], str]: """ Returns a SuperDict with the date and employee tuple as a key and the first time slot that the employee can work in the day as the value For example: {("2021-09-06", 1): "2021-09-06T07:00", ("2021-09-06", 2): "2021-09-06T13:00", ...} """ return ( self._get_employee_time_slots_week() .take([0, 2, 3]) .to_dict(0) .vapply(lambda v: min(v)) ) def get_max_working_days(self) -> SuperDict[Tuple[int, int], int]: """ Returns a SuperDict with the week and employee tuple as key and the max days worked weekly of the contract as the value Contracts are supposed to start on Monday and end on Sunday For example: {(36, 1): 5, ...} """ return self._get_employees_contract_days() def _get_incompatible_slots(self) -> TupList: """ Returns a TupList with tuples that have time slots in consecutive days where if the employee works in one time slot it can not work in the other based on the minimum resting time For example: [("2021-09-06T20:00", "2021-09-07T07:00"), ("2021-09-06T21:00", "2021-09-07T07:00"), ...] """ if ( 24 - (self._get_ending_hour() - self._get_starting_hour()) >= self._get_min_resting_hours() ): return TupList() nb_incompatible = self._hour_to_slot( int( self._get_min_resting_hours() - (24 - (self._get_ending_hour() - self._get_starting_hour())) ) ) time_slots_wo_last_day = self.time_slots.vfilter( lambda v: self._get_date_string_from_ts(v) != get_date_string_from_ts(self._get_end_date()) ) def check_same_day(ts, ts2): return ts.date() == ts2.date() def check_one_day_apart(ts, ts2): return (ts2 - ts).days <= 1 return ( TupList( [ (val, self.time_slots[pos + i]) for pos, val in enumerate(time_slots_wo_last_day) for i in range(1, nb_incompatible + 1) ] ) .vfilter(lambda v: not check_same_day(v[0], v[1])) .vfilter(lambda v: check_one_day_apart(v[0], v[1])) .vapply( lambda v: ( self._get_time_slot_string(v[0]), self._get_time_slot_string(v[1]), ) ) ) def get_incompatible_slots_employee(self) -> TupList: """ Returns a TupList with the incompatible time slots for each employee taking into account if the employee can work in both of them For example: [("2021-09-06T20:00", "2021-09-07T07:00", 1), ("2021-09-06T21:00", "2021-09-07T07:00", 1), ...] """ availability = self.get_employees_ts_availability() return TupList( [ (ts, ts2, e) for (ts, ts2) in self._get_incompatible_slots() for e in self._get_employees("name") if (ts, e) in availability and (ts2, e) in availability ] ) def get_employees_managers(self) -> TupList[int]: """Returns the list of employees ids that are managers""" return self._get_employees("manager").vfilter(lambda v: v).keys_tl() def _filter_demand(self, ts) -> float: """ Given a time slot (date time) it returns the demand, if exists, zero otherwise """ return self._get_property("demand", "demand").get( (self._get_date_string_from_ts(ts), self._get_hour_from_ts(ts)), 0 ) def get_demand(self) -> SuperDict: """ Returns a SuperDict indexed by the time slot (string) and the demand as value For example: {"2021-09-06T07:00": 10, "2021-09-06T08:00": 15, ...} """ return SuperDict( { self._get_time_slot_string(ts): self._filter_demand(ts) for ts in self.time_slots } ) def _filter_skills_demand(self, ts, id_skill) -> int: """ Given a time slot (date time) and the id of a skill, returns the skill demand if it exists, zero otherwise """ return self._get_property("skill_demand", "demand").get( (self._get_date_string_from_ts(ts), self._get_hour_from_ts(ts), id_skill), 0 ) def _employee_available(self, ts, id_employee) -> bool: """ Returns a boolean indicating if the employee is available on the time slot or not """ return (self._get_time_slot_string(ts), id_employee) in self.get_employees_ts_availability() def get_ts_demand_employees_skill(self) -> TupList: """ Returns a TupList with the combinations of: - Time slots - Skill - Demand for the given skill on this time slot - Employees that master the skill and are available on the timeslot For example: [("2021-09-06T07:00", 1, 1, [2, 3]), ("2021-09-06T08:00", 2, 1, [1, 2]), ...] """ return TupList([ ( self._get_time_slot_string(ts), id_skill, self._filter_skills_demand(ts, id_skill), self.get_employees_by_skill(id_skill).vfilter(lambda e: self._employee_available(ts, e)) ) for ts in self.time_slots for id_skill in self._get_skills() ])
class GenerationTests(unittest.TestCase): def setUp(self): super().setUp() self.full_inst_path = self._get_path("./data/instance.json") self.full_inst = SuperDict.from_dict( self.import_schema(self.full_inst_path)) # Removing parameter tables self.full_inst["properties"] = self.full_inst["properties"].vfilter( lambda v: v["type"] == "array") self.one_tab_inst_path = self._get_path("./data/one_table.json") self.one_tab_inst = SuperDict.from_dict( self.import_schema(self.one_tab_inst_path)) self.app_name = "test" self.second_app_name = "test_sec" self.default_output_path = self._get_path("./data/output") self.other_output_path = self._get_path("./data/output_path") self.last_path = self.default_output_path self.all_methods = TupList( ["getOne", "getAll", "deleteOne", "deleteAll", "update", "post"]) def tearDown(self): if os.path.isdir(self.last_path): shutil.rmtree(self.last_path) @staticmethod def _get_path(rel_path): return os.path.join(path_to_tests, rel_path) @staticmethod def import_schema(path): with open(path, "r") as fd: schema = json.load(fd) return schema def test_base(self): runner = CliRunner() result = runner.invoke( generate_from_schema, [ "-p", self.full_inst_path, "-a", self.app_name, "-o", self.other_output_path, ], ) self.assertEqual(result.exit_code, 0) self.last_path = self.other_output_path self.check(output_path=self.other_output_path) def test_one_table_schema(self): runner = CliRunner() result = runner.invoke( generate_from_schema, [ "-p", self.one_tab_inst_path, "-a", self.app_name, "-o", self.other_output_path, ], ) self.assertEqual(result.exit_code, 0) instance = SuperDict.from_dict( {"properties": { "data": self.one_tab_inst }}) self.last_path = self.other_output_path self.check(instance=instance, output_path=self.other_output_path) def test_one_table_one_option(self): runner = CliRunner() result = runner.invoke( generate_from_schema, [ "-p", self.one_tab_inst_path, "-a", self.app_name, "--one", "newname", "-o", self.other_output_path, ], ) self.assertEqual(result.exit_code, 0) instance = SuperDict.from_dict( {"properties": { "newname": self.one_tab_inst }}) self.last_path = self.other_output_path self.check(instance=instance, output_path=self.other_output_path) def test_remove_method(self): runner = CliRunner() result = runner.invoke( generate_from_schema, [ "-p", self.full_inst_path, "-a", self.second_app_name, "-o", self.other_output_path, "-r", "delete_detail", "-r", "put_detail", "-r", "get_detail", "-r", "patch_detail", ], ) self.assertEqual(result.exit_code, 0) include_methods = self.all_methods.vfilter( lambda v: v not in ["deleteOne", "update", "getOne"]) self.last_path = self.other_output_path self.check( output_path=self.other_output_path, include_methods=include_methods, app_name=self.second_app_name, ) def check(self, instance=None, output_path=None, include_methods=None, app_name=None): if app_name is None: app_name = self.app_name db = SQLAlchemy() instance = instance or self.full_inst output_path = output_path or self.default_output_path include_methods = include_methods or self.all_methods models_dir = os.path.join(output_path, "models") endpoints_dir = os.path.join(output_path, "endpoints") schemas_dir = os.path.join(output_path, "schemas") created_dirs = [output_path, models_dir, endpoints_dir, schemas_dir] # Checks that the directories have been created for path in created_dirs: self.assertTrue(os.path.isdir(self._get_path(path))) # Checks that each file has been created created_dirs = created_dirs[1:4] files = (instance["properties"].keys_tl().vapply( lambda v: (app_name + "_" + v + ".py", v))) absolute_paths = [ os.path.join(path, file) for path in created_dirs for file, _ in files ] for path_file in absolute_paths: self.assertTrue(os.path.exists(self._get_path(path_file))) if os.path.exists(path_file): with open(path_file, "r") as fd: txt = fd.read() packages_to_mock = [ "..shared.utils", ".meta_model", ".meta_resource", "..shared.const", "..shared.authentification", "..models", "..schemas", ] for package in packages_to_mock: txt = txt.replace(package, "mockedpackage") with open(path_file, "w") as fd: fd.write(txt) # Checks that the models have the correct methods and attributes for file, table in files: class_name = self.snake_to_camel(app_name + "_" + table + "_model") file_path = os.path.join(models_dir, file) spec = importlib.util.spec_from_file_location( class_name, file_path) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) # Checks correct inheritance self.assertTrue( issubclass(mod.__dict__[class_name], TraceAttributesModel)) # Checks that the all the columns are declared, have the correct type props_and_methods = mod.__dict__[class_name].__dict__ props = dict() for col in props_and_methods["__table__"]._columns: props[col.key] = next(iter(col.proxy_set)) expected_prop = instance["properties"][table]["items"][ "properties"] for prop in expected_prop: self.assertIn(prop, props) types = expected_prop[prop]["type"] if isinstance(types, list): types = TupList(types).vfilter(lambda v: v != "null")[0] type_converter = { db.String: "string", TEXT: "string", JSON: "object", Integer: "integer", db.Integer: "integer", db.Boolean: "boolean", db.SmallInteger: "integer", db.Float: "number", } actual_type = "null" for possible_type, repr_type in type_converter.items(): if isinstance(props[prop].type, possible_type): actual_type = repr_type self.assertEqual(types, actual_type) # Checks that all the methods are declared expected_methods = ["__init__", "__repr__", "__str__"] expected_methods = set(expected_methods) for method in expected_methods: self.assertIn(method, props_and_methods.keys()) # Checks that the schemas have the correct methods and attributes for file, table in files: mod_name = self.snake_to_camel(app_name + "_" + table + "_schema") class_names = [ self.snake_to_camel(app_name + "_" + table + "_" + type_schema) for type_schema in ["response", "edit_request", "post_request"] ] file_path = os.path.join(schemas_dir, file) spec = importlib.util.spec_from_file_location(mod_name, file_path) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) existing_classes = list(mod.__dict__.keys()) # Checks that all the schemas are created for class_name in class_names: self.assertIn(class_name, existing_classes) props = mod.__dict__[class_name]._declared_fields # Checks that the classes have all the attributes expected_prop = instance["properties"][table]["items"][ "properties"] expected_prop = TupList(expected_prop).vfilter( lambda v: v != "id") for prop in expected_prop: self.assertIn(prop, props) # Checks that the endpoints have all the methods for file, table in files: mod_name = self.snake_to_camel(app_name + "_" + table + "_endpoint") class_names = [ self.snake_to_camel(app_name + "_" + table + "_endpoint") ] if ("getOne" in include_methods or "deleteOne" in include_methods or "update" in include_methods): class_names.append( self.snake_to_camel(app_name + "_" + table + "_details_endpoint")) file_path = os.path.join(endpoints_dir, file) spec = importlib.util.spec_from_file_location(mod_name, file_path) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) existing_classes = list(mod.__dict__.keys()) # Checks that all the endpoints are created for class_name in class_names: self.assertIn(class_name, existing_classes) api_methods = { "get_detail": "GET", "get_list": "GET", "post_list": "POST", "delete_detail": "DELETE", "put_detail": "PUT", "patch_detail": "PATCH", } # Checks the methods of the first endpoint include_methods_e1 = [ method_name for method_name in include_methods if method_name in ["get_list", "post_list"] ] props_and_methods = mod.__dict__[class_names[0]].methods for method_name in include_methods_e1: self.assertIn(api_methods[method_name], props_and_methods) # Checks the methods of the details endpoint if len(class_names) == 2: include_methods_e2 = [ method_name for method_name in include_methods if method_name in [ "get_detail", "put_detail", "delete_detail", "patch_detail" ] ] props_and_methods = mod.__dict__[class_names[1]].methods for method_name in include_methods_e2: self.assertIn(api_methods[method_name], props_and_methods) @staticmethod def snake_to_camel(name): return "".join(word.title() for word in name.split("_"))