Beispiel #1
0
def main():
    """
    Main module code.
    """
    # pylint: disable=locally-disabled,too-many-branches
    # Parse arguments
    args = parse_args()

    # Set logger
    if args.vv:
        logging.getLogger('').setLevel(logging.DEBUG)
        logging.getLogger('sqlalchemy.engine').setLevel(logging.DEBUG)
    elif args.verbose:
        logging.getLogger('').setLevel(logging.INFO)
        # sqlalchemy INFO level is way too loud, just stick with WARNING
        logging.getLogger('sqlalchemy.engine').setLevel(logging.WARNING)
    else:
        logging.getLogger('').setLevel(logging.WARNING)
        logging.getLogger('sqlalchemy.engine').setLevel(logging.WARNING)

    # Init-config command
    if args.cmd == "init-config":
        flatisfy.config.init_config(args.output)
        sys.exit(0)
    else:
        # Load config
        if args.cmd == "build-data":
            # Data not yet built, do not use it in config checks
            config = flatisfy.config.load_config(args, check_with_data=False)
        else:
            config = flatisfy.config.load_config(args, check_with_data=True)
        if config is None:
            LOGGER.error("Invalid configuration. Exiting. "
                         "Run init-config before if this is the first time "
                         "you run Flatisfy.")
            sys.exit(1)

    # Purge command
    if args.cmd == "purge":
        cmds.purge_db(config)
        return

    # Build data files
    try:
        force = False
        if args.cmd == "build-data":
            force = True

        data.preprocess_data(config, force=force)
        LOGGER.info("Done building data!")

        if args.cmd == "build-data":
            sys.exit(0)
    except flatisfy.exceptions.DataBuildError as exc:
        LOGGER.error("%s", exc)
        sys.exit(1)

    # Fetch command
    if args.cmd == "fetch":
        # Fetch and filter flats list
        fetched_flats = fetch.fetch_flats(config)
        fetched_flats = cmds.filter_fetched_flats(config,
                                                  fetched_flats=fetched_flats,
                                                  fetch_details=True)
        # Sort by cost
        fetched_flats = {
            k: tools.sort_list_of_dicts_by(v["new"], "cost")
            for k, v in fetched_flats.items()
        }

        print(tools.pretty_json(fetched_flats))
        return
    # Filter command
    elif args.cmd == "filter":
        # Load and filter flats list
        if args.input:
            fetched_flats = fetch.load_flats_from_file(args.input, config)

            fetched_flats = cmds.filter_fetched_flats(
                config, fetched_flats=fetched_flats, fetch_details=False)

            # Sort by cost
            fetched_flats = {
                k: tools.sort_list_of_dicts_by(v["new"], "cost")
                for k, v in fetched_flats.items()
            }

            # Output to stdout
            print(tools.pretty_json(fetched_flats))
        else:
            cmds.import_and_filter(config, load_from_db=True)
        return
    # Import command
    elif args.cmd == "import":
        cmds.import_and_filter(config, load_from_db=False)
        return
    # Serve command
    elif args.cmd == "serve":
        cmds.serve(config)
        return
Beispiel #2
0
def validate_config(config, check_with_data):
    """
    Check that the config passed as argument is a valid configuration.

    :param config: A config dictionary to fetch.
    :param check_with_data: Whether we should use the available OpenData to
        check the config values.
    :return: ``True`` if the configuration is valid, ``False`` otherwise.
    """
    def _check_constraints_bounds(bounds):
        """
        Check the bounds for numeric constraints.
        """
        assert isinstance(bounds, list)
        assert len(bounds) == 2
        assert all(
            x is None or
            (
                isinstance(x, (float, int)) and
                x >= 0
            )
            for x in bounds
        )
        if bounds[0] is not None and bounds[1] is not None:
            assert bounds[1] > bounds[0]

    try:
        # Note: The traceback fetching code only handle single line asserts.
        # Then, we disable line-too-long pylint check and E501 flake8 checks
        # and use long lines whenever needed, in order to have the full assert
        # message in the log output.
        # pylint: disable=locally-disabled,line-too-long

        assert config["passes"] in [0, 1, 2, 3]
        assert config["max_entries"] is None or (isinstance(config["max_entries"], int) and config["max_entries"] > 0)  # noqa: E501

        assert config["data_directory"] is None or isinstance(config["data_directory"], str)  # noqa: E501
        assert os.path.isdir(config["data_directory"])
        assert isinstance(config["search_index"], str)
        assert config["modules_path"] is None or isinstance(config["modules_path"], str)  # noqa: E501

        assert config["database"] is None or isinstance(config["database"], str)  # noqa: E501

        assert isinstance(config["port"], int)
        assert isinstance(config["host"], str)
        assert config["webserver"] is None or isinstance(config["webserver"], str)  # noqa: E501
        assert config["backends"] is None or isinstance(config["backends"], list)  # noqa: E501

        assert isinstance(config["send_email"], bool)
        assert config["smtp_server"] is None or isinstance(config["smtp_server"], str)  # noqa: E501
        assert config["smtp_port"] is None or isinstance(config["smtp_port"], int)  # noqa: E501
        assert config["smtp_username"] is None or isinstance(config["smtp_username"], str)  # noqa: E501
        assert config["smtp_password"] is None or isinstance(config["smtp_password"], str)  # noqa: E501
        assert config["smtp_to"] is None or isinstance(config["smtp_to"], list)

        assert isinstance(config["store_personal_data"], bool)
        assert isinstance(config["max_distance_housing_station"], (int, float))
        assert isinstance(config["duplicate_threshold"], int)
        assert isinstance(config["duplicate_image_hash_threshold"], int)

        # API keys
        assert config["navitia_api_key"] is None or isinstance(config["navitia_api_key"], str)  # noqa: E501
        assert config["mapbox_api_key"] is None or isinstance(config["mapbox_api_key"], str)  # noqa: E501

        # Ensure constraints are ok
        assert config["constraints"]
        for constraint in config["constraints"].values():
            assert "type" in constraint
            assert isinstance(constraint["type"], str)
            assert constraint["type"].upper() in POSTS_TYPES.__members__

            assert "minimum_nb_photos" in constraint
            if constraint["minimum_nb_photos"]:
                assert isinstance(constraint["minimum_nb_photos"], int)
                assert constraint["minimum_nb_photos"] >= 0

            assert "description_should_contain" in constraint
            assert isinstance(constraint["description_should_contain"], list)
            if constraint["description_should_contain"]:
                for term in constraint["description_should_contain"]:
                    assert isinstance(term, str)

            assert "description_should_not_contain" in constraint
            assert isinstance(constraint["description_should_not_contain"],
                              list)
            if constraint["description_should_not_contain"]:
                for term in constraint["description_should_not_contain"]:
                    assert isinstance(term, str)

            assert "house_types" in constraint
            assert constraint["house_types"]
            for house_type in constraint["house_types"]:
                assert house_type.upper() in HOUSE_TYPES.__members__

            assert "postal_codes" in constraint
            assert constraint["postal_codes"]
            assert all(isinstance(x, str) for x in constraint["postal_codes"])
            if check_with_data:
                # Ensure data is built into db
                data.preprocess_data(config, force=False)
                # Check postal codes
                opendata_postal_codes = [
                    x.postal_code
                    for x in data.load_data(PostalCode, constraint, config)
                ]
                for postal_code in constraint["postal_codes"]:
                    assert postal_code in opendata_postal_codes  # noqa: E501

            assert "area" in constraint
            _check_constraints_bounds(constraint["area"])

            assert "cost" in constraint
            _check_constraints_bounds(constraint["cost"])

            assert "rooms" in constraint
            _check_constraints_bounds(constraint["rooms"])

            assert "bedrooms" in constraint
            _check_constraints_bounds(constraint["bedrooms"])

            assert "time_to" in constraint
            assert isinstance(constraint["time_to"], dict)
            for name, item in constraint["time_to"].items():
                assert isinstance(name, str)
                assert "gps" in item
                assert isinstance(item["gps"], list)
                assert len(item["gps"]) == 2
                assert "time" in item
                _check_constraints_bounds(item["time"])
                if "mode" in item:
                    TimeToModes[item["mode"]]

        return True
    except (AssertionError, KeyError):
        _, _, exc_traceback = sys.exc_info()
        return traceback.extract_tb(exc_traceback)[-1][-1]