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))
Example #3
0
    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
Example #4
0
    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
Example #5
0
    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