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
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]