Example #1
0
    def test_stringutils(self):
        """
        Unit test for stringutils' methods
        """
        # Check capwords
        capitalised_strings = ("Bank", "Morden East", "King's Cross St. Pancras", "Kennington Oval via Charing X")
        for test_string in capitalised_strings:
            self.assertEqual(test_string, capwords(test_string))
            self.assertEqual(test_string, capwords(test_string.lower()))
            self.assertEqual(test_string, capwords(test_string.upper()))
            self.assertNotEqual(test_string.lower(), capwords(test_string))
            self.assertNotEqual(test_string.upper(), capwords(test_string))

        # Check to see cleanup string is working
        random_string = lambda a, b: "".join([chr(random.Random().randint(a, b)) for _i in range(0, 10)])
        dirty_strings = [random_string(48, 122) for _i in range(0, 10)]
        undesirables = ("a", "b+", "[0-9]", "^x")
        for dirty_string in dirty_strings:
            cleaned_string = cleanup_name_from_undesirables(dirty_string, undesirables)
            for undesirable in undesirables:
                self.assertIsNone(re.search(undesirable, cleaned_string, flags=re.I))

        # Check string similarities - 100 for identical strings, 90 or more for one character change
        # and nothing at all for a totally unidentical string
        similarity_string = random_string(65, 122)
        self.assertEqual(get_name_similarity(similarity_string, similarity_string), 100)
        self.assertGreaterEqual(get_name_similarity(similarity_string, similarity_string[:-1]), 90)
        self.assertEqual(get_name_similarity(similarity_string, random_string(48, 57)), 0)

        # Check to see most similar string gets picked out of an list of similar-looking strings, and that
        # with very dissimilar strings, there is no candidate at all
        similarity_candidates = (similarity_string[:3], similarity_string[:5], similarity_string[:9], "z" * 10)
        self.assertEqual(get_best_fuzzy_match(similarity_string, similarity_candidates), similarity_candidates[-2])
        dissimilarity_candidates = [random_string(48, 57) for _i in range(0, 10)]
        self.assertIsNone(get_best_fuzzy_match(similarity_string, dissimilarity_candidates))

        if time.localtime().tm_isdst:
            self.assertEqual(gmt_to_localtime("2359"), "0059")
            self.assertEqual(gmt_to_localtime("23:59"), "0059")
            self.assertEqual(gmt_to_localtime("Tue 00:01"), "0101")
        else:
            self.assertEqual(gmt_to_localtime("2359"), "2359")
            self.assertEqual(gmt_to_localtime("23:59"), "2359")
            self.assertEqual(gmt_to_localtime("Tue 00:01"), "0001")
    def find_fuzzy_match(self, stop_or_station_name, params):
        """
        Find the best fuzzy match to the query_string, querying the database with dictionary params, of the format
        { Column Name : value, }. Returns an object of class returned_object, or None if no fuzzy match found
        """
        if not stop_or_station_name or stop_or_station_name == "Unknown":
            return None
        # Try to get an exact match first against station names in database
        exact_params = params.copy()
        exact_params.update({'name': stop_or_station_name})
        exact_match = self.find_exact_match(exact_params)
        if exact_match:
            return exact_match

        # Users may not give exact details, so we try to match fuzzily
        (where_statement, where_values) = self.database.make_where_statement('locations', params)
        rows = self.database.get_rows("SELECT * FROM locations WHERE %s" % where_statement, where_values)
        possible_matches = [self.returned_object(**row) for row in rows]
        best_match = get_best_fuzzy_match(stop_or_station_name, possible_matches)
        if best_match:
            return best_match
        else:
            return None
    def process_individual_request(self, requested_line, requested_origin, requested_destination, requested_direction, position):
        """
        Take an individual line, with either origin or position, and work out which station the user is
        referring to, and then get times for it. Filter trains by destination, or direction

        All arguments are strings apart from position, which is a (latitude, longitude) tuple. Return a string of
        departure data ready to send back to the user
        """
        # Try and work out line name and code if one has been requested ('Tube' is the default when we don't know)
        line_code, line_name = None, None
        if requested_line != 'Tube':
            line_name = self.line_lookup.get(requested_line, None) or get_best_fuzzy_match(requested_line, self.line_lookup.values())
            if not line_name:
                raise WhensMyTransportException('nonexistent_line', requested_line)
            line_code = get_line_code(line_name)
            if line_name != 'DLR':
                line_name += " Line"

        # Try and work out what departure station has been requested
        if position:
            logging.debug("Attempting to get closest to user's position: %s on line code %s", position, line_code)
            origin = self.get_station_by_geolocation(position, line_code)
            # There will always be a nearest station so no need to check for non-existence
        elif requested_origin:
            origin = self.get_station_by_station_name(requested_origin, line_code)
            if not origin:
                raise WhensMyTransportException('rail_station_name_not_found', requested_origin, line_name or "Tube")
            logging.debug("Found match %s on requested destination %s on line code %s", origin.name, requested_origin, line_code)
        # XXX is the code for a station that does not have TrackerNet data on the API
        if origin.code == "XXX":
            raise WhensMyTransportException('rail_station_not_in_system', origin.name)

        # If user has specified a destination, work out what it is, and check a direct route to it exists
        destination = None
        if requested_destination:
            destination = self.get_station_by_station_name(requested_destination, line_code)
            logging.debug("Found match %s on requested destination %s on line code %s", destination, requested_destination, line_code)

        # Alternatively we may have had a direction given, so try that
        direction = None
        if not destination and requested_direction:
            directions_lookup = {'n': 'Northbound', 'e': 'Eastbound', 'w': 'Westbound', 's': 'Southbound'}
            direction = directions_lookup.get(requested_direction.lower()[0], None)
            if not direction:
                raise WhensMyTransportException('invalid_direction', requested_direction)

        # We may not have been given a line - if so, try and work out what it might be from origin and destination
        if not line_code:
            lines = self.geodata.get_lines_serving(origin, destination)
            # If no lines produced, then there must be no direct route between origin and destination. This will never happen
            # if there is no destination specified, as every origin has at least one line serving it
            if not lines:
                raise WhensMyTransportException('no_direct_route', origin.name, destination.name, "Tube")
            # If more than one throw an exception due to ambiguity, then we have to ask the user for clarity
            if len(lines) > 1:
                if destination:
                    # This may never happen, as get_lines_serving() returns at most one element if a destination is given
                    raise WhensMyTransportException('no_line_specified_to', origin.name, destination.name)
                else:
                    raise WhensMyTransportException('no_line_specified', origin.name)
            line_code = lines[0]
            line_name = get_line_name(line_code)

        # Some sanity-checking, to make sure our train is actually direct
        if destination and not self.geodata.direct_route_exists(origin, destination, line_code):
            raise WhensMyTransportException('no_direct_route', origin.name, destination.name, line_name)

        # All being well, we can now get the departure data for this station and return it
        departure_data = self.get_departure_data(origin, line_code, must_stop_at=destination, direction=direction)
        if departure_data:
            return "%s to %s" % (origin.get_abbreviated_name(), str(departure_data))
        else:
            if destination:
                raise WhensMyTransportException('no_trains_shown_to', line_name, origin.name, destination.name)
            elif direction:
                raise WhensMyTransportException('no_trains_shown_in_direction', direction, line_name, origin.name)
            else:
                raise WhensMyTransportException('no_trains_shown', line_name, origin.name)