def installation_complete(repository): """Action to run when the installation is complete.""" hacs = get_hacs() hacs.hass.bus.async_fire( "hacs/repository", {"id": 1337, "action": "install", "repository": repository.data.full_name}, )
async def async_setup(hass, config): """Set up this integration using yaml.""" hacs = get_hacs() if DOMAIN not in config: return True if hacs.configuration and hacs.configuration.config_type == "flow": return True configuration = config[DOMAIN] if configuration.get(FRONTEND_REPO) and configuration.get( FRONTEND_REPO_URL): hacs.logger.critical( "Could not setup HACS, set only one of ('frontend_repo', 'frontend_repo_url)" ) return False hass.data[DOMAIN] = config hacs.hass = hass hacs.session = async_create_clientsession(hass) hacs.configuration = Configuration.from_dict(configuration) hacs.configuration.config = config hacs.configuration.config_type = "yaml" await startup_wrapper_for_yaml() return True
async def load_hacs_repository(): """Load HACS repositroy.""" hacs = get_hacs() from .const import VERSION from aiogithubapi import ( AIOGitHubAuthentication, AIOGitHubException, AIOGitHubRatelimit, ) try: repository = hacs.get_by_name("hacs/integration") if repository is None: await register_repository("hacs/integration", "integration") repository = hacs.get_by_name("hacs/integration") if repository is None: raise AIOGitHubException("Unknown error") repository.status.installed = True repository.versions.installed = VERSION repository.status.new = False hacs.repo = repository.repository_object hacs.data_repo = await get_repository(hacs.session, hacs.configuration.token, "hacs/default") except ( AIOGitHubException, AIOGitHubRatelimit, AIOGitHubAuthentication, ) as exception: hacs.logger.critical(f"[{exception}] - Could not load HACS!") return False return True
def test_check_constrains(tmpdir): hacs = get_hacs() hacs.system.ha_version = HAVERSION hacs.system.config_path = tmpdir.dirname assert not check_constrains() translations_dir = f"{hacs.system.config_path}/custom_components/hacs/translations" os.makedirs(translations_dir, exist_ok=True) custom_updater_dir = f"{hacs.system.config_path}/custom_components/custom_updater" os.makedirs(custom_updater_dir, exist_ok=True) with open(f"{custom_updater_dir}/__init__.py", "w") as cufile: cufile.write("") assert not check_constrains() temp_cleanup(tmpdir) translations_dir = f"{hacs.system.config_path}/custom_components/hacs/translations" os.makedirs(translations_dir, exist_ok=True) hacs.system.ha_version = "0.97.0" assert not check_constrains() hacs.system.ha_version = HAVERSION translations_dir = f"{hacs.system.config_path}/custom_components/hacs/translations" os.makedirs(translations_dir, exist_ok=True) assert constrain_version() assert check_constrains() assert constrain_translations() temp_cleanup(tmpdir)
async def setup_frontend(): """Configure the HACS frontend elements.""" from .http import HacsFrontend from .ws_api_handlers import setup_ws_api hacs = get_hacs() hacs.hass.http.register_view(HacsFrontend()) hacs.frontend.version_running = FE_VERSION # Add to sidepanel custom_panel_config = { "name": "hacs-frontend", "embed_iframe": False, "trust_external": False, "js_url": f"/hacsfiles/frontend-{hacs.frontend.version_running}.js", } config = {} config["_panel_custom"] = custom_panel_config hacs.hass.components.frontend.async_register_built_in_panel( component_name="custom", sidebar_title=hacs.configuration.sidepanel_title, sidebar_icon=hacs.configuration.sidepanel_icon, frontend_url_path="hacs", config=config, require_admin=True, ) if "frontend_extra_module_url" not in hacs.hass.data: hacs.hass.data["frontend_extra_module_url"] = set() hacs.hass.data["frontend_extra_module_url"].add("/hacsfiles/iconset.js") await setup_ws_api(hacs.hass)
async def async_setup_entry(hass, config_entry): """Set up this integration using UI.""" hacs = get_hacs() conf = hass.data.get(DOMAIN) if conf is not None: return False if config_entry.source == config_entries.SOURCE_IMPORT: hass.async_create_task(hass.config_entries.async_remove(config_entry.entry_id)) return False hacs.hass = hass hacs.session = async_create_clientsession(hass) hacs.configuration = Configuration.from_dict( config_entry.data, config_entry.options ) hacs.configuration.config_type = "flow" hacs.configuration.config_entry = config_entry config_entry.add_update_listener(reload_hacs) try: startup_result = await hacs_startup() except AIOGitHubAPIException: startup_result = False if not startup_result: hacs.system.disabled = True raise ConfigEntryNotReady hacs.system.disabled = False return startup_result
async def test_common_base_exception_does_not_exsist(aresponses, event_loop): aresponses.add( "api.github.com", "/rate_limit", "get", aresponses.Response(headers=response_rate_limit_header_with_limit, status=500), ) aresponses.add( "api.github.com", "/repos/test/test", "get", aresponses.Response( body=json.dumps({"message": "X"}), headers=response_rate_limit_header_with_limit, status=500, ), ) async with aiohttp.ClientSession(loop=event_loop) as session: hacs = get_hacs() hacs.session = session hacs.configuration = Configuration() hacs.configuration.token = TOKEN hacs.system.status.startup = False repository = dummy_repository_base() with pytest.raises(HacsException): await common_validate(repository)
async def test_common_blacklist(aresponses, event_loop): aresponses.add( "api.github.com", "/rate_limit", "get", aresponses.Response(body=b"{}", headers=response_rate_limit_header, status=200), ) aresponses.add( "api.github.com", "/repos/test/test", "get", aresponses.Response(body=json.dumps(repository_data), headers=response_rate_limit_header), ) async with aiohttp.ClientSession(loop=event_loop) as session: hacs = get_hacs() hacs.session = session hacs.configuration = Configuration() hacs.configuration.token = TOKEN hacs.common.blacklist.append("test/test") repository = dummy_repository_base() with pytest.raises(HacsException): await common_validate(repository) hacs.common.blacklist = []
async def async_download_file(url): """ Download files, and return the content. """ hacs = get_hacs() logger = Logger("hacs.download.downloader") if url is None: return # There is a bug somewhere... TODO: Find that bug.... if "tags/" in url: url = url.replace("tags/", "") logger.debug(f"Downloading {url}") result = None with async_timeout.timeout(60, loop=hacs.hass.loop): request = await hacs.session.get(url) # Make sure that we got a valid result if request.status == 200: result = await request.read() else: raise HacsException( "Got status code {} when trying to download {}".format( request.status, url)) return result
async def test_download_content(aresponses, tmp_path, event_loop): aresponses.add( "raw.githubusercontent.com", "/test/test/master/test/path/file.file", "get", aresponses.Response(body="test", headers=response_rate_limit_header), ) repository = dummy_repository_base() repository.content.path.remote = "" repository.content.path.local = tmp_path repository.tree = [ AIOGitHubAPIRepositoryTreeContent( { "path": "test/path/file.file", "type": "blob" }, "test/test", "master") ] async with aiohttp.ClientSession(loop=event_loop) as session: hacs = get_hacs() hacs.hass.loop = event_loop hacs.session = session await download_content(repository) assert os.path.exists( f"{repository.content.path.local}/test/path/file.file")
async def load_hacs_repository(): """Load HACS repositroy.""" hacs = get_hacs() try: repository = hacs.get_by_name("hacs/integration") if repository is None: await register_repository("hacs/integration", "integration") repository = hacs.get_by_name("hacs/integration") if repository is None: raise HacsException("Unknown error") repository.data.installed = True repository.data.installed_version = VERSION repository.data.new = False hacs.repo = repository.repository_object hacs.data_repo = await get_repository(hacs.session, hacs.configuration.token, "hacs/default") except HacsException as exception: if "403" in f"{exception}": hacs.logger.critical( "GitHub API is ratelimited, or the token is wrong.") else: hacs.logger.critical(f"[{exception}] - Could not load HACS!") return False return True
def constrain_translations(): """Check if traslations exist.""" hacs = get_hacs() if not os.path.exists( f"{hacs.system.config_path}/custom_components/hacs/.translations"): hacs.logger.critical("You are missing the translations directory.") return False return True
def read_hacs_manifest(): """Reads the HACS manifest file and returns the contents.""" hacs = get_hacs() content = {} with open(f"{hacs.system.config_path}/custom_components/hacs/manifest.json" ) as manifest: content = json.loads(manifest.read()) return content
def clear_storage(): """Clear old files from storage.""" hacs = get_hacs() storagefiles = ["hacs"] for s_f in storagefiles: path = f"{hacs.system.config_path}/.storage/{s_f}" if os.path.isfile(path): os.remove(path)
def test_clear_storage(tmpdir): hacs = get_hacs() hacs.system.config_path = tmpdir.dirname os.makedirs(f"{hacs.system.config_path}/.storage") with open(f"{hacs.system.config_path}/.storage/hacs", "w") as h_f: h_f.write("") clear_storage() os.makedirs(f"{hacs.system.config_path}/.storage/hacs") clear_storage()
async def test_common_base_exception_tree_issues(aresponses, event_loop): aresponses.add( "api.github.com", "/rate_limit", "get", aresponses.Response(body=b"{}", headers=response_rate_limit_header, status=200), ) aresponses.add( "api.github.com", "/repos/test/test", "get", aresponses.Response(body=json.dumps(repository_data), headers=response_rate_limit_header), ) aresponses.add( "api.github.com", "/rate_limit", "get", aresponses.Response(body=b"{}", headers=response_rate_limit_header, status=200), ) aresponses.add( "api.github.com", "/repos/test/test/releases", "get", aresponses.Response(body=json.dumps(release_data), headers=response_rate_limit_header), ) aresponses.add( "api.github.com", "/rate_limit", "get", aresponses.Response(body=b"{}", headers=response_rate_limit_header, status=200), ) aresponses.add( "api.github.com", "/repos/test/test/git/trees/3", "get", aresponses.Response(body=json.dumps({"message": "X"}), headers=response_rate_limit_header), ) async with aiohttp.ClientSession(loop=event_loop) as session: hacs = get_hacs() hacs.session = session hacs.configuration = Configuration() hacs.configuration.token = TOKEN repository = dummy_repository_base() hacs.common.blacklist = [] hacs.system.status.startup = False with pytest.raises(HacsException): await common_validate(repository)
def clear_storage(): """Clear old files from storage.""" hacs = get_hacs() storagefiles = ["hacs"] for s_f in storagefiles: path = f"{hacs.system.config_path}/.storage/{s_f}" if os.path.isfile(path): hacs.logger.info(f"Cleaning up old storage file {path}") os.remove(path)
def setup_extra_stores(): """Set up extra stores in HACS if enabled in Home Assistant.""" hacs = get_hacs() if "python_script" in hacs.hass.config.components: hacs.common.categories.append("python_script") if hacs.hass.services.services.get("frontend", {}).get("reload_themes") is not None: hacs.common.categories.append("theme")
def constrain_version(): """Check if the version is valid.""" hacs = get_hacs() if not version_left_higher_then_right(hacs.system.ha_version, MINIMUM_HA_VERSION): hacs.logger.critical( f"You need HA version {MINIMUM_HA_VERSION} or newer to use this integration." ) return False return True
async def get(self, request, requested_file): # pylint: disable=unused-argument """DEPRECATED.""" hacs = get_hacs() if hacs.system.ha_version.split(".")[1] >= "107": logger = Logger("hacs.deprecated") logger.warning( "The '/community_plugin/*' is deprecated and will be removed in an upcoming version of HACS, it has been replaced by '/hacsfiles/*', if you use the UI to manage your lovelace configuration, you can update this by going to the settings tab in HACS, if you use YAML to manage your lovelace configuration, you manually need to replace the URL in your resources." ) return await get_file_response(requested_file)
def constrain_custom_updater(): """Check if custom_updater exist.""" hacs = get_hacs() for location in CUSTOM_UPDATER_LOCATIONS: if os.path.exists(location.format(hacs.system.config_path)): msg = CUSTOM_UPDATER_WARNING.format( location.format(hacs.system.config_path)) hacs.logger.critical(msg) return False return True
async def validate_repository(repository, category, ref=None): """Validate.""" async with aiohttp.ClientSession() as session: hacs = get_hacs() hacs.session = session hacs.configuration = Configuration() hacs.configuration.token = TOKEN hacs.github = AIOGitHub(hacs.configuration.token, hacs.session) await register_repository(repository, category, ref=ref) print("All good!")
async def test_download_content_integration(aresponses, tmp_path, event_loop): aresponses.add( "raw.githubusercontent.com", aresponses.ANY, "get", aresponses.Response(body="", headers=response_rate_limit_header), ) aresponses.add( "raw.githubusercontent.com", aresponses.ANY, "get", aresponses.Response(body="", headers=response_rate_limit_header), ) aresponses.add( "raw.githubusercontent.com", aresponses.ANY, "get", aresponses.Response(body="", headers=response_rate_limit_header), ) aresponses.add( "raw.githubusercontent.com", aresponses.ANY, "get", aresponses.Response(body="", headers=response_rate_limit_header), ) hacs = get_hacs() hacs.system.config_path = tmp_path repository = dummy_repository_integration() repository.domain = "test" repository.content.path.local = repository.localpath repository.content.path.remote = "custom_components/test" integration_files = [ "__init__.py", "sensor.py", ".translations/en.json", "manifest.json", ] for integration_file in integration_files: repository.tree.append( AIOGithubTreeContent( { "path": f"custom_components/test/{integration_file}", "type": "blob" }, "test/test", "master", )) async with aiohttp.ClientSession(loop=event_loop) as session: hacs.hass.loop = event_loop hacs.session = session await download_content(repository) for path in repository.tree: assert os.path.exists( f"{hacs.system.config_path}/{path.full_path}")
async def get_file_response(requested_file): """Get file.""" hacs = get_hacs() if requested_file in IGNORE: hacs.logger.debug(f"Ignoring request for {requested_file}") return web.Response(status=200) if requested_file.startswith("frontend-"): if hacs.configuration.debug: servefile = await hacs.hass.async_add_executor_job(locate_debug_gz) hacs.logger.debug("Serving DEBUG frontend") elif hacs.configuration.frontend_repo: hacs.logger.debug("Serving DEVELOPMENT frontend") servefile = f"{hacs.configuration.frontend_repo}/hacs_frontend/main.js" else: servefile = await hacs.hass.async_add_executor_job(locate_gz) if os.path.exists(servefile): if hacs.configuration.frontend_repo: response = web.FileResponse(servefile) response.headers["Cache-Control"] = "no-store, max-age=0" response.headers["Pragma"] = "no-store" return response return web.FileResponse(servefile) elif requested_file == "iconset.js": return web.FileResponse( f"{hacs.system.config_path}/custom_components/hacs/iconset.js") try: if requested_file.startswith("themes"): file = f"{hacs.system.config_path}/{requested_file}" else: file = f"{hacs.system.config_path}/www/community/{requested_file}" # Serve .gz if it exist if os.path.exists(file + ".gz"): file += ".gz" if os.path.exists(file): hacs.logger.debug("Serving {} from {}".format( requested_file, file)) response = web.FileResponse(file) response.headers["Cache-Control"] = "no-store, max-age=0" response.headers["Pragma"] = "no-store" return response else: hacs.logger.error( f"Tried to serve up '{file}' but it does not exist") except Exception as error: # pylint: disable=broad-except hacs.logger.debug("there was an issue trying to serve {} - {}".format( requested_file, error)) return web.Response(status=404)
def test_translations(tmpdir): hacs = get_hacs() hacs.system.config_path = tmpdir.dirname assert not constrain_translations() translations_dir = f"{hacs.system.config_path}/custom_components/hacs/translations" os.makedirs(translations_dir, exist_ok=True) assert constrain_translations() temp_cleanup(tmpdir)
async def hacs_repositories(hass, connection, msg): """Handle get media player cover command.""" hacs = get_hacs() repositories = hacs.repositories content = [] for repo in repositories: if repo.data.category in hacs.common.categories: data = { "additional_info": repo.information.additional_info, "authors": repo.data.authors, "available_version": repo.display_available_version, "beta": repo.data.show_beta, "can_install": repo.can_install, "category": repo.data.category, "country": repo.data.country, "config_flow": repo.data.config_flow, "custom": repo.custom, "default_branch": repo.data.default_branch, "description": repo.data.description, "domain": repo.data.domain, "downloads": repo.data.downloads, "file_name": repo.data.file_name, "first_install": repo.status.first_install, "full_name": repo.data.full_name, "hide": repo.data.hide, "hide_default_branch": repo.data.hide_default_branch, "homeassistant": repo.data.homeassistant, "id": repo.data.id, "info": repo.information.info, "installed_version": repo.display_installed_version, "installed": repo.data.installed, "issues": repo.data.open_issues, "javascript_type": repo.information.javascript_type, "last_updated": repo.data.last_updated, "local_path": repo.content.path.local, "main_action": repo.main_action, "name": repo.display_name, "new": repo.data.new, "pending_upgrade": repo.pending_upgrade, "releases": repo.data.published_tags, "selected_tag": repo.data.selected_tag, "stars": repo.data.stargazers_count, "state": repo.state, "status_description": repo.display_status_description, "status": repo.display_status, "topics": repo.data.topics, "updated_info": repo.status.updated_info, "version_or_commit": repo.display_version_or_commit, } content.append(data) connection.send_message(websocket_api.result_message(msg["id"], content))
async def hacs_settings(hass, connection, msg): """Handle get media player cover command.""" hacs = get_hacs() action = msg["action"] hacs.logger.debug(f"WS action '{action}'") if action == "set_fe_grid": hacs.configuration.frontend_mode = "Grid" elif action == "onboarding_done": hacs.configuration.onboarding_done = True elif action == "set_fe_table": hacs.configuration.frontend_mode = "Table" elif action == "set_fe_compact_true": hacs.configuration.frontend_compact = False elif action == "set_fe_compact_false": hacs.configuration.frontend_compact = True elif action == "reload_data": hacs.system.status.reloading_data = True hass.bus.async_fire("hacs/status", {}) await hacs.recuring_tasks_all() hacs.system.status.reloading_data = False hass.bus.async_fire("hacs/status", {}) elif action == "upgrade_all": hacs.system.status.upgrading_all = True hacs.system.status.background_task = True hass.bus.async_fire("hacs/status", {}) for repository in hacs.repositories: if repository.pending_upgrade: repository.status.selected_tag = None await repository.install() hacs.system.status.upgrading_all = False hacs.system.status.background_task = False hass.bus.async_fire("hacs/status", {}) hass.bus.async_fire("hacs/repository", {}) elif action == "clear_new": for repo in hacs.repositories: if msg.get("category") == repo.information.category: if repo.status.new: hacs.logger.debug( f"Clearing new flag from '{repo.information.full_name}'" ) repo.status.new = False else: hacs.logger.error(f"WS action '{action}' is not valid") hass.bus.async_fire("hacs/config", {}) await hacs.data.async_write()
async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" hacs = get_hacs() if user_input is not None: return self.async_create_entry(title="", data=user_input) if hacs.configuration.config_type == "yaml": schema = {vol.Optional("not_in_use", default=""): str} else: schema = hacs_config_option_schema(self.config_entry.options) return self.async_show_form(step_id="user", data_schema=vol.Schema(schema))
async def hacs_status(hass, connection, msg): """Handle get media player cover command.""" hacs = get_hacs() content = { "startup": hacs.system.status.startup, "background_task": hacs.system.status.background_task, "lovelace_mode": hacs.system.lovelace_mode, "reloading_data": hacs.system.status.reloading_data, "upgrading_all": hacs.system.status.upgrading_all, "disabled": hacs.system.disabled, } connection.send_message(websocket_api.result_message(msg["id"], content))
async def validate_repository(repository, category, ref=None): """Validate.""" async with aiohttp.ClientSession() as session: hacs = get_hacs() hacs.session = session hacs.configuration = Configuration() hacs.configuration.token = TOKEN hacs.github = GitHub(hacs.configuration.token, hacs.session) try: await register_repository(repository, category, ref=ref, action=True) except HacsException as exception: exit(exception) print("All good!")