def test_make_profile_cache_decorator(): cache.delete_memoized(TravelerProfile.make_traveler_profile) region = 'default' traveler_type = 'standard' traveler_profile_1 = TravelerProfile.make_traveler_profile(region, traveler_type) traveler_profile_2 = TravelerProfile.make_traveler_profile(region, traveler_type) assert traveler_profile_1 is traveler_profile_2
def test_get_traveler_profile_and_override(): """ Test traveler profile's factory method make_traveler_profile and override_params when overriding args, only non-defined args will be overrided. """ region = 'default' traveler_type = 'standard' traveler_profile = TravelerProfile.make_traveler_profile(region, traveler_type) args = {'walking_speed': 42424242, 'bike_speed': 42424242} traveler_profile.override_params(args) assert(args['walking_speed'] == 42424242) assert(args['bike_speed'] == 42424242) arg_vs_profile_attr = (('bss_speed', 'bss_speed'), ('car_speed', 'car_speed'), ('max_walking_duration_to_pt', 'max_walking_duration_to_pt'), ('max_bike_duration_to_pt', 'max_bike_duration_to_pt'), ('max_bss_duration_to_pt', 'max_bss_duration_to_pt'), ('max_car_duration_to_pt', 'max_car_duration_to_pt'), ('origin_mode', 'first_section_mode'), ('destination_mode', 'last_section_mode'), ('wheelchair', 'wheelchair')) standard_profile = default_traveler_profiles['standard'] def check(arg_attr): (arg, attr) = arg_attr assert(args[arg] == getattr(standard_profile, attr)) list(map(check, arg_vs_profile_attr))
def parse_args(self, region=None, uri=None): args = self.parsers['get'].parse_args() if args.get('max_duration_to_pt') is not None: # retrocompatibility: max_duration_to_pt override all individual value by mode args['max_walking_duration_to_pt'] = args['max_duration_to_pt'] args['max_bike_duration_to_pt'] = args['max_duration_to_pt'] args['max_bss_duration_to_pt'] = args['max_duration_to_pt'] args['max_car_duration_to_pt'] = args['max_duration_to_pt'] args['max_car_no_park_duration_to_pt'] = args['max_duration_to_pt'] if args['data_freshness'] is None: # retrocompatibilty handling args['data_freshness'] = 'adapted_schedule' if args['disruption_active'] is True else 'base_schedule' # TODO : Changer le protobuff pour que ce soit propre if args['destination_mode'] == 'vls': args['destination_mode'] = 'bss' if args['origin_mode'] == 'vls': args['origin_mode'] = 'bss' # for last and first section mode retrocompatibility if 'first_section_mode' in args and args['first_section_mode']: args['origin_mode'] = args['first_section_mode'] if 'last_section_mode' in args and args['last_section_mode']: args['destination_mode'] = args['last_section_mode'] if region: if uri: objects = uri.split('/') if objects and len(objects) % 2 == 0: args['origin'] = objects[-1] else: abort(503, message="Unable to compute journeys " "from this object") # we transform the origin/destination url to add information if args['origin']: args['origin'] = transform_id(args['origin']) if args['destination']: args['destination'] = transform_id(args['destination']) if args['datetime']: args['original_datetime'] = args['datetime'] else: args['original_datetime'] = pytz.UTC.localize(args['_current_datetime']) if args.get('traveler_type'): traveler_profile = TravelerProfile.make_traveler_profile(region, args['traveler_type']) traveler_profile.override_params(args) # We set default modes for fallback modes. # The reason why we cannot put default values in parser_get.add_argument() is that, if we do so, # fallback modes will always have a value, and traveler_type will never override fallback modes. if args.get('origin_mode') is None: args['origin_mode'] = ['walking'] if args.get('destination_mode') is None: args['destination_mode'] = ['walking'] return args
def get(self, region=None, lon=None, lat=None, uri=None): args = self.parsers['get'].parse_args() if args.get('traveler_type') is not None: traveler_profile = TravelerProfile.make_traveler_profile( region, args['traveler_type']) traveler_profile.override_params(args) # We set default modes for fallback modes. # The reason why we cannot put default values in parser_get.add_argument() is that, if we do so, # fallback modes will always have a value, and traveler_type will never override fallback modes. if args.get('origin_mode') is None: args['origin_mode'] = ['walking'] if args.get('destination_mode') is None: args['destination_mode'] = ['walking'] if args['max_duration_to_pt'] is not None: #retrocompatibility: max_duration_to_pt override all individual value by mode args['max_walking_duration_to_pt'] = args['max_duration_to_pt'] args['max_bike_duration_to_pt'] = args['max_duration_to_pt'] args['max_bss_duration_to_pt'] = args['max_duration_to_pt'] args['max_car_duration_to_pt'] = args['max_duration_to_pt'] if args['data_freshness'] is None: # retrocompatibilty handling args['data_freshness'] = \ 'adapted_schedule' if args['disruption_active'] is True else 'base_schedule' # TODO : Changer le protobuff pour que ce soit propre if args['destination_mode'] == 'vls': args['destination_mode'] = 'bss' if args['origin_mode'] == 'vls': args['origin_mode'] = 'bss' #count override min_nb_journey or max_nb_journey if 'count' in args and args['count']: args['min_nb_journeys'] = args['count'] args['max_nb_journeys'] = args['count'] # for last and first section mode retrocompatibility if 'first_section_mode' in args and args['first_section_mode']: args['origin_mode'] = args['first_section_mode'] if 'last_section_mode' in args and args['last_section_mode']: args['destination_mode'] = args['last_section_mode'] if region: self.region = i_manager.get_region(region) if uri: objects = uri.split('/') if objects and len(objects) % 2 == 0: args['origin'] = objects[-1] else: abort(503, message="Unable to compute journeys " "from this object") def _set_specific_params(mod): if args.get('max_duration') is None: args['max_duration'] = mod.max_duration if args.get('_walking_transfer_penalty') is None: args[ '_walking_transfer_penalty'] = mod.walking_transfer_penalty if args.get('_night_bus_filter_base_factor') is None: args[ '_night_bus_filter_base_factor'] = mod.night_bus_filter_base_factor if args.get('_night_bus_filter_max_factor') is None: args[ '_night_bus_filter_max_factor'] = mod.night_bus_filter_max_factor if region: _set_specific_params(i_manager.instances[region]) else: _set_specific_params(default_values) if not (args['destination'] or args['origin']): abort( 400, message= "you should at least provide either a 'from' or a 'to' argument" ) if args['debug']: g.debug = True #we transform the origin/destination url to add information if args['origin']: args['origin'] = transform_id(args['origin']) if args['destination']: args['destination'] = transform_id(args['destination']) if not args['datetime']: args['datetime'] = datetime.now() args['datetime'] = args['datetime'].replace(hour=13, minute=37) if not region: #TODO how to handle lon/lat ? don't we have to override args['origin'] ? possible_regions = compute_regions(args) else: possible_regions = [region] if args['destination'] and args['origin']: api = 'journeys' else: api = 'isochrone' # we save the original datetime for debuging purpose args['original_datetime'] = args['datetime'] #we add the interpreted parameters to the stats self._register_interpreted_parameters(args) logging.getLogger(__name__).debug( "We are about to ask journeys on regions : {}".format( possible_regions)) #we want to store the different errors responses = {} for r in possible_regions: self.region = r #we store the region in the 'g' object, which is local to a request set_request_timezone(self.region) if args['debug']: # In debug we store all queried region if not hasattr(g, 'regions_called'): g.regions_called = [] g.regions_called.append(r) original_datetime = args['original_datetime'] new_datetime = self.convert_to_utc(original_datetime) args['datetime'] = date_to_timestamp(new_datetime) response = i_manager.dispatch(args, api, instance_name=self.region) if response.HasField(b'error') \ and len(possible_regions) != 1: logging.getLogger(__name__).debug( "impossible to find journeys for the region {}," " we'll try the next possible region ".format(r)) if args['debug']: # In debug we store all errors if not hasattr(g, 'errors_by_region'): g.errors_by_region = {} g.errors_by_region[r] = response.error responses[r] = response continue if all( map( lambda j: j.type in ("non_pt_walk", "non_pt_bike", "non_pt_bss", "car"), response.journeys)): responses[r] = response continue return response for response in responses.values(): if not response.HasField(b"error"): return response # if no response have been found for all the possible regions, we have a problem # if all response had the same error we give it, else we give a generic 'no solution' error first_response = responses.values()[0] if all(r.error.id == first_response.error.id for r in responses.values()): return first_response resp = response_pb2.Response() er = resp.error er.id = response_pb2.Error.no_solution er.message = "No journey found" return resp
def get(self, region=None, lon=None, lat=None, uri=None): args = self.parsers['get'].parse_args() possible_regions = compute_possible_region(region, args) logging.getLogger(__name__).debug( "Possible regions for the request : {}".format(possible_regions)) args.update(self.parse_args(region, uri)) # count override min_nb_journey or max_nb_journey if 'count' in args and args['count']: args['min_nb_journeys'] = args['count'] args['max_nb_journeys'] = args['count'] if (args['min_nb_journeys'] and args['max_nb_journeys'] and args['max_nb_journeys'] < args['min_nb_journeys']): abort(400, message='max_nb_journeyes must be >= min_nb_journeys') if args.get('timeframe_duration'): args['timeframe_duration'] = min(args['timeframe_duration'], default_values.max_duration) if not (args['destination'] or args['origin']): abort( 400, message= "you should at least provide either a 'from' or a 'to' argument" ) if args['destination'] and args['origin']: api = 'journeys' else: api = 'isochrone' if api == 'journeys' and args['origin'] == args['destination']: abort(400, message="your origin and destination points are the same") if api == 'isochrone': # we have custom default values for isochrone because they are very resource expensive if args.get('max_duration') is None: args['max_duration'] = app.config['ISOCHRONE_DEFAULT_VALUE'] if 'ridesharing' in (args['origin_mode'] or []) or 'ridesharing' in ( args['destination_mode'] or []): abort(400, message='ridesharing isn\'t available on isochrone') def _set_specific_params(mod): if args.get('max_duration') is None: args['max_duration'] = mod.max_duration if args.get('_arrival_transfer_penalty') is None: args[ '_arrival_transfer_penalty'] = mod.arrival_transfer_penalty if args.get('_walking_transfer_penalty') is None: args[ '_walking_transfer_penalty'] = mod.walking_transfer_penalty if args.get('_night_bus_filter_base_factor') is None: args[ '_night_bus_filter_base_factor'] = mod.night_bus_filter_base_factor if args.get('_night_bus_filter_max_factor') is None: args[ '_night_bus_filter_max_factor'] = mod.night_bus_filter_max_factor if args.get('_max_additional_connections') is None: args[ '_max_additional_connections'] = mod.max_additional_connections if args.get('min_nb_journeys') is None: args['min_nb_journeys'] = mod.min_nb_journeys if args.get('max_nb_journeys') is None: args['max_nb_journeys'] = mod.max_nb_journeys if args.get('_min_journeys_calls') is None: args['_min_journeys_calls'] = mod.min_journeys_calls if args.get('_max_successive_physical_mode') is None: args[ '_max_successive_physical_mode'] = mod.max_successive_physical_mode if args.get('_final_line_filter') is None: args['_final_line_filter'] = mod.final_line_filter if args.get('max_extra_second_pass') is None: args['max_extra_second_pass'] = mod.max_extra_second_pass if args.get('additional_time_after_first_section_taxi') is None: args[ 'additional_time_after_first_section_taxi'] = mod.additional_time_after_first_section_taxi if args.get('additional_time_before_last_section_taxi') is None: args[ 'additional_time_before_last_section_taxi'] = mod.additional_time_before_last_section_taxi if args.get('_stop_points_nearby_duration') is None: args[ '_stop_points_nearby_duration'] = mod.stop_points_nearby_duration # we create a new arg for internal usage, only used by distributed scenario args[ 'max_nb_crowfly_by_mode'] = mod.max_nb_crowfly_by_mode # it's a dict of str vs int for mode in fallback_modes.all_fallback_modes: nb_crowfly = args.get('_max_nb_crowfly_by_{}'.format(mode)) if nb_crowfly is not None: args['max_nb_crowfly_by_mode'][mode] = nb_crowfly # activated only for distributed for mode in FallbackModes.modes_str(): tmp = 'max_{}_direct_path_duration'.format(mode) if args.get(tmp) is None: args[tmp] = getattr(mod, tmp) for mode in FallbackModes.modes_str(): tmp = 'max_{}_direct_path_distance'.format(mode) if args.get(tmp) is None: args[tmp] = getattr(mod, tmp) if args.get('_transfer_path') is None: args['_transfer_path'] = mod.transfer_path if args.get('_access_points') is None: args['_access_points'] = mod.access_points if args.get('_pt_planner') is None: args['_pt_planner'] = mod.default_pt_planner if args.get('_asgard_language') is None: args['_asgard_language'] = mod.asgard_language if args.get('bss_rent_duration') is None: args['bss_rent_duration'] = mod.bss_rent_duration if args.get('bss_rent_penalty') is None: args['bss_rent_penalty'] = mod.bss_rent_penalty if args.get('bss_return_duration') is None: args['bss_return_duration'] = mod.bss_return_duration if args.get('bss_return_penalty') is None: args['bss_return_penalty'] = mod.bss_return_penalty if args.get('_filter_odt_journeys') is None: args['_filter_odt_journeys'] = mod.filter_odt_journeys # When computing 'same_journey_schedules'(is_journey_schedules=True), some parameters need to be overridden # because they are contradictory to the request if args.get("is_journey_schedules"): # '_final_line_filter' (defined in db) removes journeys with the same lines sequence args["_final_line_filter"] = False # 'no_shared_section' removes journeys with a section that have the same origin and destination stop points args["no_shared_section"] = False if args['debug']: g.debug = True # Add the interpreted parameters to the stats self._register_interpreted_parameters(args) logger = logging.getLogger(__name__) # generate an id that : # - depends on the coverage and differents arguments of the request (from, to, datetime, etc.) # - does not depends on the order of the arguments in the request (only on their values) # - does not depend on the _override_scenario argument # - if _override_scenario is present, its type is added at the end of the id in # order to identify identical requests made with the different scenarios # - we add the current_datetime of the request so as to differentiate # the same request made at distinct moments def generate_request_id(): path = str(request.path) args_for_id = dict(request.args) if "_override_scenario" in args_for_id: scenario = str(args_for_id["_override_scenario"][0]) args_for_id["_override_scenario"] = "" else: scenario = "new_default" json_repr = json.dumps(args_for_id, sort_keys=True, ensure_ascii=True) # we could use the json_repr as an id, but we hash it to have something smaller m = hashlib.sha256() m.update(json_repr.encode("UTF-8")) json_hash = m.hexdigest() now = dt_to_str(datetime.datetime.utcnow()) result = "journeys_{}_{}#{}#".format(json_hash, now, scenario) logger.info("Generating id : {} for request : {}".format( result, request.url)) return result request_id = generate_request_id() args["request_id"] = request_id # If there are several possible regions to query: # copy base request arguments before setting region specific parameters if len(possible_regions) > 1: base_args = deepcopy(args) # Store the different errors responses = {} for r in possible_regions: self.region = r if args.get('traveler_type'): traveler_profile = TravelerProfile.make_traveler_profile( region, args['traveler_type']) traveler_profile.override_params(args) # We set default modes for fallback modes. # The reason why we cannot put default values in parser_get.add_argument() is that, if we do so, # fallback modes will always have a value, and traveler_type will never override fallback modes. args['origin_mode'] = args.get('origin_mode') or ['walking'] args['destination_mode'] = args['destination_mode'] or ['walking'] _set_specific_params(i_manager.instances[r]) set_request_timezone(self.region) logging.getLogger(__name__).debug("Querying region : {}".format(r)) # Save the original datetime for debuging purpose original_datetime = args['original_datetime'] if original_datetime: new_datetime = self.convert_to_utc(original_datetime) args['datetime'] = date_to_timestamp(new_datetime) scenario_name = i_manager.get_instance_scenario_name( self.region, args.get('_override_scenario')) if scenario_name == "new_default" and ( "taxi" in args["origin_mode"] or "taxi" in args["destination_mode"]): abort( 400, message="taxi is not available with new_default scenario") response = i_manager.dispatch(args, api, instance_name=self.region) # Store the region in the 'g' object, which is local to a request if args['debug']: instance = i_manager.instances.get(self.region) # In debug we store all queried region if not hasattr(g, 'regions_called'): g.regions_called = [] region = { "name": instance.name, "scenario": instance._scenario_name } g.regions_called.append(region) # If journeys list is empty and error field not exist, we create # the error message field if not response.journeys and not response.HasField(str('error')): logging.getLogger(__name__).debug( "impossible to find journeys for the region {}," " insert error field in response ".format(r)) response.error.id = response_pb2.Error.no_solution response.error.message = "no solution found for this journey" response.response_type = response_pb2.NO_SOLUTION if response.HasField(str('error')) and len(possible_regions) > 1: if args['debug']: # In debug we store all errors if not hasattr(g, 'errors_by_region'): g.errors_by_region = {} g.errors_by_region[r] = response.error logging.getLogger(__name__).debug( "impossible to find journeys for the region {}," " we'll try the next possible region ".format(r)) responses[r] = response # Before requesting the next region, reset args before region specific settings args = base_args continue # If every journeys found doesn't use PT, request the next possible region non_pt_types = ("non_pt_walk", "non_pt_bike", "non_pt_bss", "car") if all(j.type in non_pt_types for j in response.journeys) or all( "non_pt" in j.tags for j in response.journeys): responses[r] = response if len(possible_regions) > 1: # Before requesting the next region, reset args before region specific settings args = base_args continue if args['equipment_details']: # Manage equipments in stop points from the journeys sections instance = i_manager.instances.get(self.region) return instance.equipment_provider_manager.manage_equipments_for_journeys( response) return response for response in responses.values(): if not response.HasField(str("error")): return response # if no response have been found for all the possible regions, we have a problem # if all response had the same error we give it, else we give a generic 'no solution' error first_response = list(responses.values())[0] if all(r.error.id == first_response.error.id for r in responses.values()): return first_response resp = response_pb2.Response() er = resp.error er.id = response_pb2.Error.no_solution er.message = "No journey found" return resp