From fda2e89a25c1d4d51ef13649681f9464c7e82d6b Mon Sep 17 00:00:00 2001 From: fananchonun Date: Sat, 25 Sep 2021 21:50:28 +0800 Subject: [PATCH] Merge next into master for 0.10.0 (#196) * add new integration: Hass-Custom-Alarm (#152) as per https://github.com/akasma74/Hass-Custom-Alarm/pull/11#issuecomment-504210287 * Set DEV as version * add new integration: Hass-Custom-Alarm (#152) as per https://github.com/akasma74/Hass-Custom-Alarm/pull/11#issuecomment-504210287 * Set DEV as version * Adding remote control custom card (#156) * Add alexa_media_player (#159) * Add alexa_media_player * Adds ',' * Init testing * Add option to set minimum HA version in manifest * Add note to documentation about min HA version * multi deploy? * missing * push it? * Add pipline config as a listener * Clean dir * Speed up repository page load * Default back to None * Adding Meross IoT library (#161) Proposing the Meross HomeAssistant Custom component as default one. * test * Fix load tests * Add iphonedetect-integration (#162) * Add integration for "Virgin Tivo" (#163) * Add philips_android_tv integration (#166) * docs? * simplify * set 3bf1223 * Update const.py (#168) Added Flo water sensor * Fix TimeoutError issue * added ljmerza cards (#174) * Badges * Add beta info * generic * Remove log * Adds Ceerbeerus/beerbolaget * Fix changelog links * Add claytonjn/hass-circadian_lighting (#180) to default integration repositories * Add thomasloven/hass-browser_mod to default repo (#179) * remove TODO * Update repository.py (#185) * Update const.py (#186) * Don't use pre-release (#187) `git describe --tags --always $(git rev-list --tags --max-count=1000) | grep -e "[0-9]\+\.[0-9]\+\.[0-9]\+$"` returns ``` 0.9.0 0.8.1 0.8.0 0.7.0 0.6.0 0.5.1 0.5.0 0.4.4 0.4.3 0.4.2 0.4.1 0.4.0 0.2.1 0.2.0 0.1.0 ``` * move to data * use data branch for lists * Change descrription * remove examples * format * pipeline updates * fix custom * Simplify migration * add restart steps * Don't return to settings for HACS * Fix ref * Change dev mode * Fix download issue for plugins * I have no clue what this does --- .github/ISSUE_TEMPLATE/issue.md | 5 +- .gitignore | 4 +- .pipelines/documentation.yml | 4 + .pipelines/load_hacs_35.yml | 5 +- .pipelines/load_hacs_36.yml | 5 +- .pipelines/load_hacs_37.yml | 5 +- .pipelines/pytest.yml | 25 +++++ README.md | 6 +- custom_components/hacs/__init__.py | 6 +- custom_components/hacs/aiogithub.py | 6 +- custom_components/hacs/const.py | 61 +------------ .../hacs/frontend/elements/hacs.css | 14 +++ custom_components/hacs/frontend/views/api.py | 9 -- .../hacs/frontend/views/repository.py | 86 ++++++++++++------ .../hacs/frontend/views/settings.py | 9 +- .../hacs/frontend/views/store.py | 13 ++- custom_components/hacs/hacsbase.py | 48 +++++++--- custom_components/hacs/hacsmigration.py | 81 +++++++++-------- custom_components/hacs/hacsrepositorybase.py | 47 +++++++--- .../hacs/hacsrepositoryintegration.py | 1 + custom_components/hacs/hacsstorage.py | 6 +- custom_components/hacs/handler/log.py | 36 -------- custom_components/hacs/manifest.json | 2 +- docs/developer/blacklist_repository.md | 2 +- .../developer/include_default_repositories.md | 20 +--- docs/developer/integration.md | 14 +++ docs/images/min_version_warning.png | Bin 0 -> 12524 bytes docs/installation/configuration.md | 2 + docs/installation/manual.md | 6 +- docs/installation/terminal.md | 4 +- info.md | 10 +- requirements.txt | 5 +- tests/__init__.py | 1 + tests/data/__init__.py | 1 + tests/data/aiogithub.py | 16 ++++ tests/test_aiogithub.py | 31 +++++++ 36 files changed, 352 insertions(+), 244 deletions(-) create mode 100644 .pipelines/pytest.yml delete mode 100644 custom_components/hacs/handler/log.py create mode 100644 docs/images/min_version_warning.png create mode 100644 tests/__init__.py create mode 100644 tests/data/__init__.py create mode 100644 tests/data/aiogithub.py create mode 100644 tests/test_aiogithub.py diff --git a/.github/ISSUE_TEMPLATE/issue.md b/.github/ISSUE_TEMPLATE/issue.md index 81b14001..5b50cd16 100644 --- a/.github/ISSUE_TEMPLATE/issue.md +++ b/.github/ISSUE_TEMPLATE/issue.md @@ -14,6 +14,7 @@ Issues not containing the minimum requirements will be closed: --> ## Version of HACS + @@ -25,9 +26,7 @@ A clear and concise description of what the bug is. ## Debug log - + ```text diff --git a/.gitignore b/.gitignore index d97e1006..51a7ac0e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -site \ No newline at end of file +site +.pytest* +__pycache__ \ No newline at end of file diff --git a/.pipelines/documentation.yml b/.pipelines/documentation.yml index d4c7626b..bd63989c 100644 --- a/.pipelines/documentation.yml +++ b/.pipelines/documentation.yml @@ -1,4 +1,8 @@ trigger: + branches: + include: + - master + - next paths: include: - docs/* diff --git a/.pipelines/load_hacs_35.yml b/.pipelines/load_hacs_35.yml index faaaa9de..b1cc1361 100644 --- a/.pipelines/load_hacs_35.yml +++ b/.pipelines/load_hacs_35.yml @@ -1,5 +1,8 @@ trigger: - - "*" + branches: + include: + - master + - next pool: vmImage: 'ubuntu-16.04' diff --git a/.pipelines/load_hacs_36.yml b/.pipelines/load_hacs_36.yml index 3a383caf..a416e5f7 100644 --- a/.pipelines/load_hacs_36.yml +++ b/.pipelines/load_hacs_36.yml @@ -1,5 +1,8 @@ trigger: - - "*" + branches: + include: + - master + - next pool: vmImage: 'ubuntu-16.04' diff --git a/.pipelines/load_hacs_37.yml b/.pipelines/load_hacs_37.yml index cb0fb11b..e76832e1 100644 --- a/.pipelines/load_hacs_37.yml +++ b/.pipelines/load_hacs_37.yml @@ -1,5 +1,8 @@ trigger: - - "*" + branches: + include: + - master + - next pool: vmImage: 'ubuntu-16.04' diff --git a/.pipelines/pytest.yml b/.pipelines/pytest.yml new file mode 100644 index 00000000..42b958bc --- /dev/null +++ b/.pipelines/pytest.yml @@ -0,0 +1,25 @@ +trigger: + branches: + include: + - master + - next + +pool: + vmImage: 'ubuntu-16.04' + +steps: + - task: UsePythonVersion@0 + displayName: 'Set 3.7.x as Python version' + inputs: + versionSpec: '3.7.x' + + - script: python3 -m pip install --upgrade pip setuptools wheel + displayName: 'Install tools' + + - script: | + python3 -m pip install homeassistant + python3 -m pip install -r requirements.txt + displayName: 'Install dependencies' + + - script: "python3 -m pytest" + displayName: 'Run PyTest' \ No newline at end of file diff --git a/README.md b/README.md index 03860302..0bea66c1 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,9 @@ HACS is a component that gives the user a powerful UI to handle downloads of cus **Highlights of what HACS can do:** -- Help you discover new integrations and plugins. -- Help you install (download) new integrations and plugins. -- Help you keep track of your integrations and plugins. +- Help you discover new custom elements. +- Help you install (download) new custom elements. +- Help you keep track of your custom elements. - Manage(Install/Upgrade/Remove) - Shortcuts to repositories/issue tracker diff --git a/custom_components/hacs/__init__.py b/custom_components/hacs/__init__.py index dbcac679..01e05981 100644 --- a/custom_components/hacs/__init__.py +++ b/custom_components/hacs/__init__.py @@ -30,7 +30,6 @@ ELEMENT_TYPES, VERSION, IFRAME, - BLACKLIST, ) from .frontend.views import ( @@ -47,7 +46,7 @@ DOMAIN = "{}".format(NAME_SHORT.lower()) # TODO: Remove this when minimum HA version is > 0.93 -REQUIREMENTS = ["aiofiles", "backoff"] +REQUIREMENTS = ["aiofiles==0.4.0", "backoff==1.8.0", "packaging==19.0"] _LOGGER = logging.getLogger("custom_components.hacs") @@ -156,6 +155,7 @@ async def configure_hacs(hass, github_token, hass_config_dir): github_token, hass.loop, async_create_clientsession(hass) ) + hacs.hacs_github = await hacs.aiogithub.get_repo("custom-components/hacs") + hacs.hass = hass hacs.config_dir = hass_config_dir - hacs.blacklist = BLACKLIST diff --git a/custom_components/hacs/aiogithub.py b/custom_components/hacs/aiogithub.py index 828886f8..0bdab4d4 100644 --- a/custom_components/hacs/aiogithub.py +++ b/custom_components/hacs/aiogithub.py @@ -1,8 +1,8 @@ """Async Github API implementation.""" -# pylint: disable=super-init-not-called,missing-docstring,invalid-name +# pylint: disable=super-init-not-called,missing-docstring,invalid-name,redefined-builtin import base64 import logging -from asyncio import CancelledError +from asyncio import CancelledError, TimeoutError from datetime import datetime import async_timeout @@ -186,7 +186,7 @@ async def get_contents(self, path, ref=None): params = {"path": path} if ref is not None: - params["ref"] = ref + params["ref"] = ref.replace("tags/", "") async with async_timeout.timeout(20, loop=self.loop): response = await self.session.get(url, headers=self.headers, params=params) diff --git a/custom_components/hacs/const.py b/custom_components/hacs/const.py index ac3ba0a8..805e8d34 100644 --- a/custom_components/hacs/const.py +++ b/custom_components/hacs/const.py @@ -1,8 +1,8 @@ """Constants for HACS""" -VERSION = "0.9.0" +VERSION = "DEV" NAME_LONG = "HACS (Home Assistant Community Store)" NAME_SHORT = "HACS" -STORAGE_VERSION = "2" +STORAGE_VERSION = "3" STORENAME = "hacs" PROJECT_URL = "https://github.com/custom-components/hacs/" CUSTOM_UPDATER_LOCATIONS = [ @@ -12,15 +12,7 @@ GENERIC_ERROR = "Possible error codes: 1D10T, PICNIC, B0110CK5." ISSUE_URL = "{}issues".format(PROJECT_URL) DOMAIN_DATA = "{}_data".format(NAME_SHORT.lower()) -BLACKLIST = [ - "custom-cards/boilerplate-card", - "custom-cards/custom-card-helpers", - "custom-cards/information", - "custom-cards/tracker-card", - "custom-components/blueprint", - "custom-components/information", - "custom-components/custom_updater", -] + ELEMENT_TYPES = ["integration", "plugin"] IFRAME = { "title": "Community", @@ -37,6 +29,8 @@ To use this you need to remove custom_updater form {} """ +NOT_SUPPORTED_HA_VERSION = "You have version '{}' of Home Assistant, but version '{}' of '{}' require version '{}' of Home Assistant, install and upgrades are disabled for this integration untill you upgrade Home Assistant." + STARTUP = """ ------------------------------------------------------------------- {} @@ -66,48 +60,3 @@ "If we knew what it was we were doing, it would not be called research, would it?", "Wait a minute, Doc. Ah… Are you telling me you built a time machine… out of a DeLorean?", ] - - -################################ -## Extra default repositories # -################################ - -DEFAULT_REPOSITORIES = { - "appdaemon": [ - "apop880/SmartThings-Button", - "apop880/White-Noise", - "apop880/Night-Mode", - ], - "integration": [ - "StyraHem/ShellyForHASS", - "isabellaalstrom/sensor.krisinformation", - "JurajNyiri/HomeAssistant-Tavos", - "JurajNyiri/HomeAssistant-Atrea", - "TimSoethout/goodwe-sems-home-assistant", - "bramkragten/lyric", - "bramkragten/mind", - "bouwew/sems2mqtt", - ], - "plugin": [ - "maykar/compact-custom-header", - "maykar/lovelace-swipe-navigation", - "peternijssen/lovelace-postnl-card", - "nervetattoo/simple-thermostat", - "nervetattoo/banner-card", - "kalkih/mini-media-player", - "kalkih/mini-graph-card", - "finity69x2/fan-control-entity-row", - "thomasloven/lovelace-card-mod", - "thomasloven/lovelace-markdown-mod", - "thomasloven/lovelace-slider-entity-row", - "thomasloven/lovelace-fold-entity-row", - "isabellaalstrom/krisinfo-card", - "tcarlsen/lovelace-light-with-profiles", - "atomic7777/atomic_calendar", - "bramkragten/weather-card", - "bramkragten/swipe-card", - "CyrisXD/love-lock-card", - ], - "python_script": [], - "theme": [], -} diff --git a/custom_components/hacs/frontend/elements/hacs.css b/custom_components/hacs/frontend/elements/hacs.css index 7bbea60d..967c558d 100644 --- a/custom_components/hacs/frontend/elements/hacs.css +++ b/custom_components/hacs/frontend/elements/hacs.css @@ -19,6 +19,20 @@ grid-gap: 10px; } +.hacs-grid-badge { + float: right; + transform: rotate(45deg); + font-size: 12px; + width: 50px; + text-align: center; + transform-origin: 50px 37px; +} + +.hacs-table-badge { + margin-left: 5px; + font-size: 12px; +} + .hacs-overview-container { width: 95%; margin-left: 2.5%; diff --git a/custom_components/hacs/frontend/views/api.py b/custom_components/hacs/frontend/views/api.py index 193dadda..5d7ba7be 100644 --- a/custom_components/hacs/frontend/views/api.py +++ b/custom_components/hacs/frontend/views/api.py @@ -27,8 +27,6 @@ async def get( repository = self.repositories[action] await repository.install() await self.storage.set() - if action == "172733314": - raise web.HTTPFound(self.url_path["settings"]) raise web.HTTPFound( "{}/{}".format(self.url_path["repository"], repository.repository_id) ) @@ -126,13 +124,6 @@ async def get( jsons[repository.repository_id][item] = var[item] return self.json(jsons) - elif element == "log" and action == "get": - from ...handler.log import get_log_file_content - - content = self.base_content - content += await get_log_file_content(self.config_dir) - return web.Response(body=content, content_type="text/html", charset="utf-8") - raise web.HTTPFound(self.url_path["error"]) async def post( diff --git a/custom_components/hacs/frontend/views/repository.py b/custom_components/hacs/frontend/views/repository.py index e6330ca5..2cf851fe 100644 --- a/custom_components/hacs/frontend/views/repository.py +++ b/custom_components/hacs/frontend/views/repository.py @@ -2,7 +2,11 @@ # pylint: disable=broad-except import logging from aiohttp import web +from packaging.version import Version +from homeassistant.const import __version__ as HAVERSION + from ...blueprints import HacsViewBase +from ...const import NOT_SUPPORTED_HA_VERSION _LOGGER = logging.getLogger("custom_components.hacs.frontend") @@ -38,6 +42,8 @@ async def get(self, request, repository_id): try: message = request.rel_url.query.get("message") repository = self.repositories[str(repository_id)] + repository.new = False + await self.storage.set() if message != None: custom_message = """ @@ -80,23 +86,7 @@ async def get(self, request, repository_id): pending_restart = "" if repository.additional_info: - if repository.info is None: - info = "
" + await self.aiogithub.render_markdown( - repository.additional_info - ) - info = info.replace("

", "

").replace("
", "") - info = info.replace("

", "

").replace("
", "") - info = info.replace("

", "

").replace("

", "") - info = info.replace("", "") - info = info.replace( - '", "") - info = info.replace("", "") - repository.info = info - else: - info = repository.info + info = repository.additional_info else: info = "" @@ -180,12 +170,25 @@ async def get(self, request, repository_id): else repository.repository_type ) + main_action = """ + + {} + + """ + if not repository.installed: - main_action = "INSTALL" + main_action = main_action.format( + self.url_path["api"], repository.repository_id, "INSTALL" + ) elif repository.pending_update: - main_action = "UPGRADE" + main_action = main_action.format( + self.url_path["api"], repository.repository_id, "UPGRADE" + ) else: - main_action = "REINSTALL" + main_action = main_action.format( + self.url_path["api"], repository.repository_id, "REINSTALL" + ) if repository.repository_type == "plugin": if not repository.installed: @@ -240,6 +243,38 @@ async def get(self, request, repository_id): content = self.base_content + if ( + repository.homeassistant_version is not None + and repository.last_release_tag is not None + ): + if Version(HAVERSION[0:6]) < Version( + str(repository.homeassistant_version) + ): + content += """ + + """.format( + NOT_SUPPORTED_HA_VERSION.format( + HAVERSION, + repository.last_release_tag, + repository.name, + str(repository.homeassistant_version), + ) + ) + main_action = main_action.replace( + "Reload {} {} -
  • Open a issue
  • +
  • Open issue
  • Flag this
  • @@ -310,10 +345,7 @@ async def get(self, request, repository_id): {}
    - - {} - + {} {} repository {} @@ -343,8 +375,6 @@ async def get(self, request, repository_id): info, authors, note, - self.url_path["api"], - repository.repository_id, main_action, changelog, repository.repository_name, diff --git a/custom_components/hacs/frontend/views/settings.py b/custom_components/hacs/frontend/views/settings.py index e2b0012b..cfb12301 100644 --- a/custom_components/hacs/frontend/views/settings.py +++ b/custom_components/hacs/frontend/views/settings.py @@ -214,16 +214,9 @@ async def get(self, request): HACS REPO - - OPEN LOG -
    """.format( - modal1, - self.url_path["api"], - upgrade_all_btn, - ISSUE_URL, - self.url_path["api"], + modal1, self.url_path["api"], upgrade_all_btn, ISSUE_URL ) ## Integration URL's diff --git a/custom_components/hacs/frontend/views/store.py b/custom_components/hacs/frontend/views/store.py index 94595551..9b629719 100644 --- a/custom_components/hacs/frontend/views/store.py +++ b/custom_components/hacs/frontend/views/store.py @@ -62,14 +62,18 @@ async def get(self, request): # pylint: disable=unused-argument else: card_icon = "" + badge = "" + if self.data.get("hacs", {}).get("view") == "Table": + if repository.new: + badge = 'NEW' card = """ {} - {} + {}{} {} """.format( @@ -84,19 +88,21 @@ async def get(self, request): # pylint: disable=unused-argument else repository.name.replace("-", " ") .replace("_", " ") .title(), + badge, repository.description, ) card += "" else: - + if repository.new: + badge = 'NEW' card = """
    - {} {} + {} {}{}

    {}

    @@ -114,6 +120,7 @@ async def get(self, request): # pylint: disable=unused-argument else repository.name.replace("-", " ") .replace("_", " ") .title(), + badge, repository.description, ) diff --git a/custom_components/hacs/hacsbase.py b/custom_components/hacs/hacsbase.py index ece18a9b..e27124b8 100644 --- a/custom_components/hacs/hacsbase.py +++ b/custom_components/hacs/hacsbase.py @@ -2,11 +2,12 @@ # pylint: disable=too-few-public-methods,unused-argument import logging import uuid +import json import os from datetime import timedelta from homeassistant.helpers.event import async_track_time_interval, async_call_later from .aiogithub import AIOGitHubException, AIOGitHubRatelimit -from .const import DEFAULT_REPOSITORIES, ELEMENT_TYPES +from .const import ELEMENT_TYPES _LOGGER = logging.getLogger("custom_components.hacs.hacs") @@ -22,9 +23,11 @@ class HacsBase: data = {"hacs": {}} data["task_running"] = True hass = None + _default_repositories = [] config_dir = None aiogithub = None blacklist = [] + hacs_github = None repositories = {} url_path = {} @@ -53,11 +56,21 @@ async def startup_tasks(self, notarealargument=None): self.data["hacs"]["endpoints"] = self.url_path try: - # Check for updates to HACS. - repository = await self.aiogithub.get_repo("custom-components/hacs") + _LOGGER.info("Trying to load existing data.") + + # Check if migration is needed, or load existing data. + await self.migration.validate() - repository = HacsRepositoryIntegration("custom-components/hacs", repository) + # Check for updates to HACS. + repository = HacsRepositoryIntegration( + "custom-components/hacs", self.hacs_github + ) await repository.setup_repository() + old = await self.storage.get(True) + old_hacs = old.get("repositories", {}).get(repository.repository_id, {}) + if old_hacs.get("show_beta", False): + repository.show_beta = old_hacs.get("show_beta", False) + await repository.update() self.repositories[repository.repository_id] = repository # After an upgrade from < 0.7.0 some files are missing. @@ -69,10 +82,7 @@ async def startup_tasks(self, notarealargument=None): _LOGGER.critical("HACS is missing files, trying to correct.") await repository.install() - _LOGGER.info("Trying to load existing data.") - - # Check if migration is needed, or load existing data. - await self.migration.validate() + await self.storage.get() except AIOGitHubRatelimit as exception: _LOGGER.critical(exception) @@ -215,6 +225,15 @@ async def get_repositories(self): "theme": [], } + _LOGGER.info("Fetching updated blacklist") + blacklist = await self.hacs_github.get_contents( + "repositories/blacklist", "data" + ) + + for item in json.loads(blacklist.content): + if item not in self.blacklist: + self.blacklist.append(item) + # Get org repositories if not self.dev: repositories["integration"] = await self.aiogithub.get_org_repos( @@ -222,10 +241,15 @@ async def get_repositories(self): ) repositories["plugin"] = await self.aiogithub.get_org_repos("custom-cards") - # Additional repositories - for repository_type in DEFAULT_REPOSITORIES: - if repository_type in ELEMENT_TYPES: - for repository in DEFAULT_REPOSITORIES[repository_type]: + # Additional default repositories + for repository_type in ELEMENT_TYPES: + _LOGGER.info("Fetching updated %s repository list", repository_type) + default_repositories = await self.hacs_github.get_contents( + "repositories/{}".format(repository_type), "data" + ) + for repository in json.loads(default_repositories.content): + if repository not in self._default_repositories: + self._default_repositories.append(repository) result = await self.aiogithub.get_repo(repository) repositories[repository_type].append(result) diff --git a/custom_components/hacs/hacsmigration.py b/custom_components/hacs/hacsmigration.py index 772dded4..1dc0efb1 100644 --- a/custom_components/hacs/hacsmigration.py +++ b/custom_components/hacs/hacsmigration.py @@ -1,9 +1,12 @@ """Blueprint for HacsMigration.""" import logging +import json from shutil import copy2 +import aiofiles + from .hacsbase import HacsBase -from .const import STORAGE_VERSION, VERSION +from .const import STORAGE_VERSION, STORENAME _LOGGER = logging.getLogger("custom_components.hacs.migration") @@ -11,70 +14,76 @@ class HacsMigration(HacsBase): """HACS data migration handler.""" - old = None + _old = None async def validate(self): """Check the current storage version to determine if migration is needed.""" - self.old = await self.storage.get() + self._old = await self.storage.get(True) - if not self.old: + if not self._old: # Could not read the current file, it probably does not exist. # Running full scan. await self.update_repositories() - elif "schema" not in self.old["hacs"]: + elif self._old["hacs"]["schema"] == "1": # Creating backup. source = "{}/.storage/hacs".format(self.config_dir) - destination = "{}.none".format(source) + destination = "{}.1".format(source) _LOGGER.info("Backing up current file to '%s'", destination) copy2(source, destination) + await self.from_1_to_2() - # Run migration. - await self.from_none_to_1() - - # Run the rest. - await self.update_repositories() - - elif self.old["hacs"]["schema"] == "1": + elif self._old["hacs"]["schema"] == "2": # Creating backup. source = "{}/.storage/hacs".format(self.config_dir) - destination = "{}.1".format(source) + destination = "{}.2".format(source) _LOGGER.info("Backing up current file to '%s'", destination) copy2(source, destination) - await self.from_1_to_2() + await self.from_2_to_3() - elif self.old["hacs"].get("schema") == STORAGE_VERSION: + elif self._old["hacs"].get("schema") == STORAGE_VERSION: pass else: # Should not get here, but do a full scan just in case... await self.update_repositories() - async def from_none_to_1(self): - """Migrate from None (< 0.4.0) to storage version 1.""" - _LOGGER.info("Starting migration of HACS data from None to 1.") + await self.flush_data() + + async def flush_data(self): + """Flush validated data.""" + _LOGGER.info("Flushing data to storage.") - for item in self.old["elements"]: - repodata = self.old["elements"][item] - if repodata.get("isinstalled"): - # Register new repository - _LOGGER.info("Migrating %s", repodata["repo"]) - repository, setup_result = await self.register_new_repository( - repodata["element_type"], repodata["repo"] - ) + datastore = "{}/.storage/{}".format(self.config_dir, STORENAME) - repository.version_installed = repodata["installed_version"] - repository.installed = True - self.repositories[repository.repository_id] = repository + try: + async with aiofiles.open( + datastore, mode="w", encoding="utf-8", errors="ignore" + ) as outfile: + await outfile.write(json.dumps(self._old, indent=4)) + outfile.close() + + except Exception as error: + msg = "Could not write data to {} - {}".format(datastore, error) + _LOGGER.error(msg) async def from_1_to_2(self): """Migrate from storage version 1 to storage version 2.""" _LOGGER.info("Starting migration of HACS data from 1 to 2.") + self.data = self._old - for repository in self.repositories: - repository = self.repositories[repository] - repository.show_beta = False - await repository.set_repository_releases() - self.repositories[repository.repository_id] = repository - self.data["hacs"]["schema"] = "2" + for repository in self._old["repositories"]: + self._old["repositories"][repository]["show_beta"] = False + self._old["hacs"]["schema"] = "2" _LOGGER.info("Migration of HACS data from 1 to 2 is complete.") + + async def from_2_to_3(self): + """Migrate from storage version 2 to storage version 3.""" + _LOGGER.info("Starting migration of HACS data from 2 to 3.") + self.data = self._old + + for repository in self._old["repositories"]: + if self._old["repositories"][repository]["installed"]: + self._old["repositories"][repository]["new"] = False + self._old["hacs"]["schema"] = "2" + _LOGGER.info("Migration of HACS data from 2 to 3 is complete.") diff --git a/custom_components/hacs/hacsrepositorybase.py b/custom_components/hacs/hacsrepositorybase.py index 8265463d..600617dd 100644 --- a/custom_components/hacs/hacsrepositorybase.py +++ b/custom_components/hacs/hacsrepositorybase.py @@ -6,7 +6,8 @@ import pathlib import os import shutil - +from packaging.version import Version +from homeassistant.const import __version__ as HAVERSION from .aiogithub import AIOGitHubException from .hacsbase import HacsBase from .exceptions import ( @@ -16,7 +17,7 @@ HacsBlacklistException, ) from .handler.download import async_download_file, async_save_file -from .const import DEFAULT_REPOSITORIES, VERSION +from .const import VERSION, NOT_SUPPORTED_HA_VERSION _LOGGER = logging.getLogger("custom_components.hacs.repository") @@ -38,7 +39,9 @@ def __init__(self): self.last_release_object = None self.last_release_tag = None self.last_updated = None + self.homeassistant_version = None self.name = None + self.new = True self.pending_restart = False self.reasons = [] self.releases = None @@ -64,15 +67,7 @@ def custom(self): """Return flag if the repository is custom.""" if self.repository_name.split("/")[0] in ["custom-components", "custom-cards"]: return False - elif self.repository_name in DEFAULT_REPOSITORIES["appdaemon"]: - return False - elif self.repository_name in DEFAULT_REPOSITORIES["integration"]: - return False - elif self.repository_name in DEFAULT_REPOSITORIES["plugin"]: - return False - elif self.repository_name in DEFAULT_REPOSITORIES["python_script"]: - return False - elif self.repository_name in DEFAULT_REPOSITORIES["theme"]: + elif self.repository_name in self._default_repositories: return False return True @@ -178,8 +173,20 @@ async def common_update(self): try: # Set additional info await self.set_additional_info() + if self.additional_info is not None: + info = await self.aiogithub.render_markdown(self.additional_info) + info = info.replace("

    ", "

    ").replace("
    ", "") + info = info.replace("

    ", "

    ").replace("
    ", "") + info = info.replace("

    ", "

    ").replace("

    ", "") + info = info.replace("", "") + info = info.replace( + '
    ", "") + info = info.replace("", "") + self.additional_info = info except AIOGitHubException: - pass + self.additional_info = None async def download_repository_directory_content( self, repository_directory_path, local_directory, ref @@ -198,7 +205,7 @@ async def download_repository_directory_content( ) for content_object in contents: - if content_object.type == "dir": + if content_object.type == "dir" and self.content_path != "": await self.download_repository_directory_content( content_object.path, local_directory, ref ) @@ -258,6 +265,20 @@ async def install(self): # Run update await self.update() # pylint: disable=no-member + if ( + self.homeassistant_version is not None + and self.last_release_tag is not None + ): + if Version(HAVERSION[0:6]) < Version(str(self.homeassistant_version)): + message = NOT_SUPPORTED_HA_VERSION.format( + HAVERSION, + self.last_release_tag, + self.name, + str(self.homeassistant_version), + ) + _LOGGER.error(message) + return False + # Check local directory await self.check_local_directory() diff --git a/custom_components/hacs/hacsrepositoryintegration.py b/custom_components/hacs/hacsrepositoryintegration.py index a60cc411..8ddc829b 100644 --- a/custom_components/hacs/hacsrepositoryintegration.py +++ b/custom_components/hacs/hacsrepositoryintegration.py @@ -71,6 +71,7 @@ async def set_manifest_content(self): self.authors = manifest["codeowners"] self.name = manifest["name"] self.domain = manifest["domain"] + self.homeassistant_version = manifest.get("homeassistant") return raise HacsRequirement("manifest.json does not contain expected values.") diff --git a/custom_components/hacs/hacsstorage.py b/custom_components/hacs/hacsstorage.py index ad4b337c..f38d5379 100644 --- a/custom_components/hacs/hacsstorage.py +++ b/custom_components/hacs/hacsstorage.py @@ -14,7 +14,7 @@ class HacsStorage(HacsBase): """HACS storage handler.""" - async def get(self): + async def get(self, raw=False): """Read HACS data to storage.""" from .blueprints import ( HacsRepositoryAppDaemon, @@ -39,6 +39,9 @@ async def get(self): # Issues reading the file (if it exists.) return False + if raw: + return store_data + # Restore data about HACS self.data["hacs"]["schema"] = store_data["hacs"].get("schema") self.data["hacs"]["view"] = store_data["hacs"].get("view") @@ -157,6 +160,7 @@ async def set(self): repositorydata["repository_name"] = repository.repository_name repositorydata["repository_type"] = repository.repository_type repositorydata["show_beta"] = repository.show_beta + repositorydata["new"] = repository.new repositorydata["version_installed"] = repository.version_installed data["repositories"][repository.repository_id] = repositorydata diff --git a/custom_components/hacs/handler/log.py b/custom_components/hacs/handler/log.py deleted file mode 100644 index 7ec0bfff..00000000 --- a/custom_components/hacs/handler/log.py +++ /dev/null @@ -1,36 +0,0 @@ -"""Log handler.""" -# pylint: disable=broad-except -import logging -import aiofiles - -from ..const import STARTUP - -_LOGGER = logging.getLogger("custom_components.hacs.log") - - -async def get_log_file_content(config_dir): - """Get logfile content.""" - log_file = "{}/home-assistant.log".format(config_dir) - - interesting = "
    {}
    ".format(STARTUP) - - try: - async with aiofiles.open( - log_file, mode="r", encoding="utf-8", errors="ignore" - ) as localfile: - logfile = await localfile.readlines() - localfile.close() - for line in logfile: - if "[custom_components.hacs" in line or "[homeassistant.core" in line: - line = line.replace("(MainThread)", "") - line = line.replace(" DEBUG ", "") - line = line.replace(" INFO ", "") - line = line.replace(" WARNING ", "") - line = line.replace(" ERROR ", "") - line = line.replace(" CRITICAL ", "") - interesting += "
    {}
    ".format( - line - ) - except Exception as exception: - _LOGGER.error(exception) - return interesting diff --git a/custom_components/hacs/manifest.json b/custom_components/hacs/manifest.json index 40024373..dee7ee8a 100644 --- a/custom_components/hacs/manifest.json +++ b/custom_components/hacs/manifest.json @@ -4,5 +4,5 @@ "documentation": "https://custom-components.github.io/hacs", "dependencies": [], "codeowners": ["@ludeeus"], - "requirements": ["aiofiles==0.4.0", "backoff==1.8.0"] + "requirements": ["aiofiles==0.4.0", "backoff==1.8.0", "packaging==19.0"] } \ No newline at end of file diff --git a/docs/developer/blacklist_repository.md b/docs/developer/blacklist_repository.md index f83b3d36..30d9a8b6 100644 --- a/docs/developer/blacklist_repository.md +++ b/docs/developer/blacklist_repository.md @@ -4,7 +4,7 @@ As a developer you can blacklist your repositories. A blacklisted repository can not be added to HACS. -To add a repository to the blacklist add it to `BLACKLIST` at the bottom of the [`const.py file`](https://github.com/custom-components/hacs/blob/next/custom_components/hacs/const.py) +To add a repository to the blacklist add it to the `blacklist` file in the [`data` branch](https://github.com/custom-components/hacs/blob/data/repositories) _NB!: The list is case sensitive._ diff --git a/docs/developer/include_default_repositories.md b/docs/developer/include_default_repositories.md index f1fae20b..e3763a88 100644 --- a/docs/developer/include_default_repositories.md +++ b/docs/developer/include_default_repositories.md @@ -6,24 +6,8 @@ Before you try to add your repository to the default store first make sure that Only the owner of the repository or a major contributor to it can submit a PR to have it included as a default. -When all of this is covered, you can add it to `DEFAULT_REPOSITORIES` at the bottom of the [`const.py file`](https://github.com/custom-components/hacs/blob/next/custom_components/hacs/const.py) +When all of this is covered, you can add it to repository type files in the [`data` branch](https://github.com/custom-components/hacs/blob/data/repositories) _NB!: The list is case sensitive._ -When a PR for this is merged, it will be a part of the next planned minor release (0.X.0), if no release is planed a release will be created about a week after the first addition. - -_Contributions for the integration should go against the `next` branch._ - -**Examples:** - -- [`AppDaemon App`](https://github.com/custom-components/hacs/pull/139) -- [`Integration`](https://github.com/custom-components/hacs/pull/64) -- [`Plugin`](https://github.com/custom-components/hacs/pull/65) - - - - \ No newline at end of file +When a PR for this is merged, it will show up in HACS after the first scheduled scan (every 500min). diff --git a/docs/developer/integration.md b/docs/developer/integration.md index fdceed6f..6a331e79 100644 --- a/docs/developer/integration.md +++ b/docs/developer/integration.md @@ -53,6 +53,20 @@ In the integration directory, there is a [`manifest.json`](https://developers.ho When installing/upgrading it will scan the content in the latest release. +If you are using releases you can also set a minimum HA version in the `manifest.json` file, example: + +https://github.com/ludeeus/integration-hacs/blob/0.2.0/custom_components/integration-hacs/manifest.json + +```json +{ + "homeassistant": "0.96.0" +} +``` + +If the user try to install this and the minimum HA requirement is not met, the user will receive a warning and it will block install/upgrade until HA is upgraded. + +![min_version](../images/min_version_warning.png) + #### If there are no releases It will scan files in the branch marked as default. diff --git a/docs/images/min_version_warning.png b/docs/images/min_version_warning.png new file mode 100644 index 0000000000000000000000000000000000000000..0b684ffb2ccd51be29bd1a83fb8e590766adef0f GIT binary patch literal 12524 zcmcJ0cT`hbw=aT%4Ny@~0|bu+&QU=;htPw-0rV)%fG8wHDWY@;2`vE?6#+?9R8UG# zlukmC8c0-1=tT$tLI4R62t6T`K;VV*ec!!r+&kVI?~Z%lAK7b{wdY!MuC>>i^EZEq zv9Yp{-g|tnn3$OKmCJu#7ZVd#6P;J?mJ}Vuv+Mp66*~~uElk7+?TS;Pi=7_Emd0XY zMYw$%cXo-c_xNAFg%A^ysonnVp!#LIiHTi!c;!!HyAbEu)Tt70htQ#U;O)!0XYk^$ z&z`a;=-6NRttso;8;u;BH#_xh_TQC{+H<>bk7ShQiKADqk>8$vvE%pM7osFD+&&i* zn)Wzp=b=M9l~JRN4EWiO9Cq~zEIb(B&0YepfPoRTa3a6mYqr#3ey*E6D4gF~xpCt0 z0h8TVe*wG}6(`PNy=_Fr<3gQYz-uXk5n#UZYPu9rd1(YLy$M1^HTaRQE@=t0;KkL(~AA+ z+s)hwNLgoZjl#>6w(ssNS!bn%va#~7HUs7&R$Au&(atTms6*QwsSlU~f)^hC`wO2`*&WYK zMNbOPwHt0OTHn*(u6AJAjQr0R{*OKXm&AWnpw<9hub2K)k^eLr|8?^XY6!3Y(^~Q( zYt<|OC~MDX+T_r6j;fJxjWbTHsD2|FH2XF&gsFn>RXAz0V5cO3&G!FM>6Q-qOeKREw8h| zhb|D{HM7$z*lEK=x7m)pTg$`3b-vFt&q1ekegY;$DRs+*HrM_o;=xzNxvKLjUNSG;i;|n7Y68?{b5ln z0A zPOh%$eqf6$e@t%6qFzjyB=PThZYeO!pbp1 zz_jMiitE(a0dD2SY)}Q>Yk0&V%w=pSf1(^pBFJ5A zyhuPNe$zhWe3p*qMHz%G*`rIiz)e=9Bkn8@hRhhsm&)HaCufjm*(=?aiuDEW0X_7V z%pxbDh0@xD*&ZCT?448^4&H(nw0h8mRaO|x*C7XAA?L&Nt!eMX?f`m{PcQp`c}k9| zT9sHuW8`WEKA()B!t$&mClJ#P>f#}c(s`XvJk=HFuY(Iyt}jhDm;Hu|$o_+ygKd62 z#3TY~lblp>r@KL6Vau#Z zulp|bj3Dsq-yN5UMhhQkk((N_u$KFnPJ^YYfZ6JqaiY$`zF3cLBnvhlF<*smlZ#j# z)nP9bZ}F(n-uk$;0%`%ZXhJ;+l5beI8XdVrksT;cm1@p%ZuwHOBmKtYFSSRztkk{F z7!tZ9$1A*ALDPuckd#X4W{kp+VP1JPe;|sfLxkop1w1UV>1@|;(1;u-*JI7T`Yig+ zoz}qge_IOxsr7k$ApdMw0fu=j7DfwkG&W`7bE)$DMIp1+b}2;IM>Q(-Rn^!WFxLAj zZoS#_vxd&}&z6lmryM*OO6MftH)nQPdrUM)h4;C?45U;;#!{X6q5Y7wGV-nBbWKp2 zzM#%px$euXq&t8!nrDzL$LE4^aeDMc6Wo(TOX2{M<$kVmN1=+>Pjk5TK9GOax9@Rq z?vK**zBjzRd$2NyNQ~o%*YSFOO9X#_Hm0$A3;Zfs5x{Wx~j-{D4rt*H#dQ z8BnquucoI_v!eGt{84cZa70nqWnnNM(Se)>q{4si>p6ajtWGRq1Tp<`1Swj9Op&wejA{vgau_R~$; zFqwC*xH73E6$gk@-1jxkdE=ES_f40i8v(8vkT9qGVcHrjgq>G3!zngCsrOj=Y;pck zHH4QX#?`Kx60Ji+?})=sjJ9?~3mybVohyuxi|7a^!q@Wvzdux(5l!U7$0g)1)$55L zbHgm!EAy4|f7fS=8NIrX4|@1UU346)P+7hAef@qnX@~Nz zb?K~+yN&`GONe&y+Lis)Fg11GBqDhQmOX&`NL|_c?BNqa^FE(uj>!X~AU^YPbI4ahxL8uMmFjQ{T+dIzD^*4D2>_ID(_@I@QXFBN@j z`vM*cp39^C3}E)idKVjiGa4=#*>eQwTDZdvoqem^xc@Hm08{b3X>zQi7pA8{@?HEz zAB$9}*gVo(Vbf{K>DunT-%eIZH;Wd-jGk@vx%z4u0LSulgV`xc&F0NVDEZ6skLRvB zPpU*o`{AeYaeJyNuphIko`a5!f*A_F&laeO9O#W}@wh;|X_ezw^WpFZ7;l?TMy0DJ z4jwdO9P&5y@hPNzLD#!l7TyF-PRRgwjY?)p9}^Bvk~H>an36y{dPZG=>tnkRyQ&<0 zI8vRa6yHB~0xZ(5tEqylyuZ#d4#TsxwNMF~+!e@_bXV|dEknxOsmt-P36c8tv5sV*93FhpM4>5plY2gT02j8S zu!8FVs4nReZ!)iEU5Z9|SIj}Tpw}MkkI+C{kW+`U)sOTq@A7WP%U`9u+&jKGJC6<) zD#aZXTrco#hPc*rNHA{iWD8rV zG4AR<*LyVWl?|YwGU7`}Pw0h2*$b+DV#O1$BwRZZYipb#fY%8(U9pZGe>-G{!Ls9s zt}fTWZyCYL-+}{46=KROtEzQG?_Yy*z`#fU}F|6_f!60G>KX3t;*~O++H{ZVN+uv;yJ3v zyg}`F3OC))m+HR_Ge385bRG@Rk{|qm49P~Bd)VL z`jPoZ#Y-2G)8W#Qd{+uRdZo!@rT#)ln^y;M)|dC>;^flOuIe0(!utX2yJNTxwx@Qf z@jF38?QRSToH%TE?MOo@AWDWtXNx{kQ;h2L!f6Ncj3Xj7ci0Zflr)3i8<#AL1KM=M zr-I-k$fd5@9!cr4egp0wdy@xedO+Irx<*;*7bM+9oA7A9!C!7@MRM2DOBq*FW=vj0 z=vp@KcDF#!;NS^`AX@J~^sy4@)0E?Dvy=#c=JPvP94pG^9)NfFj)j0)yl{=C;Qcgh z>yZKhXuIA9pTB$a+^U>8(=L>TU#IO;980LU`C#MjHL_qfT5w*NFhuPS(&Pe}r&k}I_>_nqCIi&*)viC}$3}b5>PO_pY zjLsfmIEy(>uBtsTd(;saIzB2M(cj7ECrXYOU96*?t1@QQA`f?Cj7E?hVJAi{s>Y<8 zAV!{F3Y7N&=b_Jb>5kWb{Lpu+ZdzQ+LI3vRclE#x_np9p_igJn$hmOm7$9X+39#Yy zyI2~K>m79SE88XTDBX9z-nP&6v?rnJH+9Yg?@?LKBF7~)*6WM$2e2UiFcBBAu|GT+ z{b^$BXySl=safrL^yt6I~;YS8vvL1}r_OUKLLX zn&3XF9N01M-~VjNT-fHvjF=?tSRMD6t-Mk>+*J`UXB02n^LFs&&Ra_&Gnu<~KHOi- zbh!-cSK}>;SN=k6du8@6gtVr;y;_(94%=W1WSr+Kc!Zzu3Z00P^AV2=Oo4`lwjsSG z_>u_6XOv341w*xlcZ=rOBDYOgurOYs?v|HXO0wYK249JCHx@V&y^oJ6Qm9@qo1QPZ z69=9+Og|r)do?w<)cAyYTAF9-XpNj%3?C^CWe(^~j^Y%xm3#78Oy4Od>U#u#&qn3*+nX0~dX+v6 z&_xnBY`Bs|*f%i=3L!94GOb{wCG*gExq6URL$b*nQ?Kxkw+70L;+93gw~8uZ&h(c@ zX=4);4X>HUYi<446>9t7`%{`F-M<{Xb7(c*&*64r+^e{fX<3FXHRj2sxNrBoN#OHu z)ms9xoN~0K<>lH-(|z}d*qrLfTRr!2$N@Yvag z=es@(<(u`6F>duEEi08& z&c0${&8Q&lLfCWa6@uT@S}*aRMlJKqsVc|YOO1q~>PbXoY##4)_>U}j7>#iIV>erl-1`6=>6U{UIohA>x7KKk zm$zy4EbtZf7-P)qOX59DiW}6a_C4@32IjoCZ{bqZCvUMuJ8PP;psRGDsaQ`AvZ+hV{{BO|BzFOi8iZvJ7g6HI+ zrhZxn{6epXFiydJZs@1*a^J@x!jOZp)6f72VI(N zjwyZU&_6$Rdb+3uH)bG{sH)J^O1qX0agUKm?pt1tMW3~1>!6;nCAbi39`<01!uMIdKL6->(5L)W!sO*1+*#i1z= z&vOJ&sa4G_S#09kGwjj>l)&(*Cyvfz6QRIvI9rZdEITJb0x!q5xh_B_Y%rMpJ(*}G z#UB`J%qKRctswFht8XkFjOLtXDC1YAQ^Nkb5zsxy6Q4}bXo6TbEjY4WscIhWZxYKL zJY_~JP@m^n_S2WoUiW)vE<} zykU7dge4CE9$IenBy~A3wTrGWZ?0OT>&L!R)as}TL*zSVOGow{tIv$^le5oi(0Vi9 zNT6O;<~trr^O}|G`)(BAKM0%Wuv0=f)k+@(y^1=CmfrT^bFR7Ij{_q|0VkOQ^Hmul zO)Ks2>QR*TWwo^C%UGVC_Sua0wE;8VFSk?U$d$SZR(R2-E|y>xr^&u{=nOESusNrO zl#LFwuiA5q@yFD(^ds;JJHUU2iw#mbqYn<{$K~_RkLBg<^HFEbLc&HupL+*~9U1dv zW~uuQRD`M-4eerOsmp(ii*gj@Exr3C&vH%0!OJ948}H`ONMs;y`PEX@ z=zH}hU1}M+iPqcdny%Rcu+&N277F{fLJfvLTWV9E#%o6ep*1Ahj6%Oa?!K|I2n@hntNSzCR4YeD%CXsNqj3MlonSTWeOd+G#E zS#JpJau}kW2?3FAieX$Vg6*!B&vQBRF0Uz85i&~~4QiZ(yZ!dG~e7ZU@0%zvK)h^a``AxyQH{gM<^8;#pjjykG zQ4?W0h&{4~LU|=K@DizxaDPCmVLJ_EZz>hq|7vD1#OGpK@hx{l^PfeiGVMjxF*-wM zKF9)*=?Wmd(NlQL_(W_kDvVqv8+VPZG@-6pq83=Y2&P0J~tQhZFT;lOEYhEnhl z&G~TM)xi{AbNLj~xvP%)c2o-6)PxDlM{JOQGYj?3iS{z;`w;y6G7BFi8oB*QFn@fc zcG?+{tF@Tq31pKix}4c{YFWp5XLzm!hfeiCnQ)H; z9Ag8jGR^AY!-Q2{18X5`M~HeshPUx3XQnz;MVPZ;=QRE?Vtg9qPAsEUUNXo?&X^&Q z+23@RYtmBIH}W)bm+++@8m`YqHRP4V8%#p^`Nfm#$eqr~?aP^!mr6S-*%Rbve$AOJ z?ft1Hi(cMh7@+mcZEN6g2@%NXYBQ_e>ZU^ST1@jlEN6xc);+=x`fdFy?_3C7k1fZ_6GRVQb! zAle-3&*!grMUOewMJVo8%lb_LjpD)TbK+5ynCZ3`;1kA2n3)4q@l4h?oq|(nZH!aViRfuZU@3S zfY+T6Vtm!YMYsi`cm0nEbteV>uX;{(-`4^Z*p{PT%QtixWBvmNG`7?hbV~c*ihLV0 zSIhvP+3S}o#n;olkAkgSed29XHlm}eD0L!>^oVO_e#%USf&%*O^A+C0$F+yzrSPegDNuc}S)p>IaIfv1wrN{W~v^PFyGq#OlN21PyYkhZBt4q-iiBASeE z@a>#Rr;4r)Z6y?;1r_(vyOwEEbHu2led!|#PRRohv}dAnQK)h%xOnIc(@_>@h8jlA z>dOemvp50Ql7sZ0neyu-%NBkXt?8iFup7;31LM@$Y;39X8%Lx1FIQUaMSF#VWP;VvX3FgeJc70&PT*DfjJApt1w zkHOt-<%)I2%B%J@A)nKll`!1=MZ8hu_=n@TNlynK+EGuR*9qPW9N{b5WX~K$?4c-# zaIskz6f{#71LqUSc?RtF7FwB(Qsbv#W9){oomIYoPuBRB>j_ow7LRv#NcZ?%ON%z0 z(E^Ww`qP8fKCpO5Ij70Ir&xBKE|FaJ02ed%CU5j)Wc^JRi{@o=I|&+0f!&vcoZ zIOfxQ=TnC!`4m<6wrO#xb02?@Hm`Zxnu`b_{HP}{h0<2DDJ|u^IIQ4dlGos?m9Q&A z#|p9#n!#+Y;kd@qxw}yxV|F6BC)oQA6PcsG9V;hpdM4~~bpNeU@X3VA3Nl(9zo}XE z+}j_jK;>k(e$tMmO#Iba)tG9D4(=JSk2Hy}C|#@wDJTj-uYP}u_69_i8^^~uwB-=g zhbTFI>6XQ`td>R~)#BaB&Zb|>93@KE(GxfKX{^TYsnWboYT2>&>MW|d#)Lw@USTQN z5X{@moLu-l_X%Tubk3y?h;l2L$PqbBT6j(Zs!fXySQAIz_cj+i@YQ~brM7#urGnzy z?5-&_Hl+o*?;9L?J!`q~BYJ;vX@fH{*I~-#ecUa+oN`=w^nH2$L7cz~5Ex;cF?`R1rkB}_RYKVqT&W5OcHty0&17!j6o)(EF`Oz@I*v&sbXsz7+s zb9T_`Nf>fb0&|?@YtRXCYI9{Ab%SI(&%px~gHc}sCSBfGB}N4TX?K!!M@4Q_ZshnC ze2e&U^@=B+In29<4FHSAHl<%WcIh)K-9bUBzo*VP)9Pr@pl_-jjXR4=YHVhOnU}CM5%A)p6eW;0aJTBG zX?fh&=)}bI8yWmBaUG0>?lw(aJ)`yykHDz!X9dVZyUU4Xm3sy(^YwB%7x(oGpSuJC zU{IKoyA`qFv)}~8OZSP!HAv4u+uEcj1o@2S^Jx8qwYQ4Hx?XtlC4=X>ApR4xftoUV zsoeS4nUC{jReBHDLu+Yq7M*Z*A$7D^Y3y@oPd{fMo%d6>(%bzt1A6XF*Pxlg`s%T^ zgMT}MfiEd9+YV+xGiN$89d(?8-9&M&@O_J&?~elEkfdUGP39#l*7}7 z_y{0fUBehpXJC+vp-yyJ$maK?-%E^h;w0r7rBWP}ZF#?imd6`vpwCHUY=xty$Yni_xSS$=>T`#G~V~x=Tk&+Q*{@8*PP* zhIHOu%|)lfX~7B_n`@(rR9gAUj)3P46Q3rYWiRWl9CyvilLC9r>N$NaqKoO=z_-8Y zK5VbxnKH&?_9!i3_M15jt6Q%Txjz>5E?aEsC1->>u&wC9Lg{&51Y zLx?9F9P6d!2Lp@Ju2uAErjtzXiE{htG1sS-_0LkRC<8Vmsue)Zy!CMHn{JC=kG?r7bNdq}KTSAoaVY0p~CR^!bEqNgg6(4D)qos?*r_ zsf(pAo*Zab9(=$zn%{9Rp)=JU~J@+kN*?gvO52KAs7Fc5STn_ z1Yzep&b<1D zwUO3!Ie1RMP_?L8)>nVaANkZM`Psk7*9Nsit7~+KUmKthioP}lq;Kw?5j-k+z3Y|E z+Xao|o|txVL}{KY`Z{9Tg{?E)dO7%4Iy}19ctSn^b&)i2s`p`uzokdi^+rke=jyFF zaombH1Mq|h!Va*tCtbazV(oHAZ?DH4&Ae6`0lOH$p8MP!(EIM?+;sv)#)YtjUjC#T zgB&>r?anGQ{&1l5sI$s>nf=ufY2=~(rJ**FLSjwz$J)rEPw=wPH&&PwkOu@slTBXM zU9Br0tCpV(3UElRH2caMJNoG)Fqf@1=ZF){bz*E@FNBqCMjqgS`-9+oqs#hfK(%s5 zRXW`y8s=;W%wB9FEPXkUL+rhxX{WCom2uo<*eArdc&c;1rUmB`CMOaH+!9>}CcBT`Cv+3TLcmCLdCZZ8Xldt{ z1gCqs6FG!!i-J+E2>TX4H{1${YX zq4tA{a%ZUzVD+byX*iANFw7BCj}GLU3SL!B>4 zlN8a4ZW-<6jtPSHCLez*AiKIblWOvO&XgSs1DUgA8U>Y{-(vwUh^z~_SwWB4pFqTV zTd(Mj;|X*#%&YmO9`lGVXXS~q;25*ADPF{-@-mUNX4+wKjlB`T;8X{r{KVsO-fKFo z{rTL|h9fgq=s971yfE>wMwL9x?6vTCH(qv;cvY?g&0&9WMxrWZA z>LPUNA~l&n`x}{h+bpuwaW($>*Y<95T3s)IBU*M{sf zM^PWTApp%A!^{w5dS1W_>6JXtn9d5cD&oYJFC1epRrwplSY9be*CH!VHn14ZTj@JJ ze{pC&>=J-G#O87Z|s6S4;gpa`L z1C9`<&VYnBLk5owRKCP^_Nh4dgm|Zs1f&o{dFS#NnsBYsyC))xx2w@}?tm&HxBcY8 z+Ku&{Wee7diu59WM0-{sY;WB9GmCl&;6i0Yd+4nEUdfnmKRzY4rdYbY;O{d(Q0}Hn zeY-w$iRyDXt6>&(R%^%#K0@SaDrgs{c35sK{1%F3VsD=t5~fY}mK9Ew>C6Wn?(0eE zTD1c-$svl_RynXnMZ%06oRqB_B#NDJ#!W3YnvAWvkox@N&bRFBRsY+-f35K))fleP>XC z{vtmwd72$^Dg%m9SJC$<>6U|Gpyg%jeT}}V4j9sYmb2gLQU>DnO^an{8n0b4w9|Q- zt;J6=kWHtwyT5>)pfGRJKr|EK)v;?nHo4>##E|sfFml4u6_AJAv6-gL-A(aB@!7+e zfaj4|ufbSjAtD{_B?SSe#{rZQ5sXynsYY4is}9gX=_u^zsN$`}9F!sUc!oa(HVCP_9S1I- zittQLFGB`|&e0-8j-amRNNAU{7mHhLv5k{`OED;mNmYbCjTjpYrgsE*L^&2IUv5C8 zmi&5a%J&hKwb~c5-`=-byYj+jnutQBc>0DL=697UBoMe{zg}!?#AY5<3ogIvl-kng z4+G-ceSIM3dnkX3fcYOsq7bEB#L?VgDR)nmDOPoJkO=9RO=#V#pBf(%wT z!|SK!RjiG;qPQN_UX+1&Xh>_t?P8Z&wl)QW-xy(>^9FFUFXK7EPRYuwGb(3c(MbzW z;7#@;*87RTj1AevyQ zc4lfC3ifPOhw%K@(=29*x~KykBJ>S;K%tRXe(+?OJK3zH{GK@euQbA5QnGNn! zG3Thf8#tphAVRmR>*A~3be0iPPCt;Gz93Xr-dff9piGb&qwX)K0{dB#07+LAMC~u- zVMtwq5tOdvlw6jJh-9*s3+MZjiX9l$5h>x&AN|^*jd@3Gn_KUb@dUH_A9(O11=&A# zbBK{~L(;L+7mnR(#QbRQ-{}G|QoC!pFfjBJ^a8TEgbc`o>Df&Gy!ZXD8>O>o(dU6_ zglMe2kskQu#A?7(Z%xPfP{gX$Cv!G43`B_lnqDYn<0MP(2u==|npU1Ol&b+Ar%X&{ zKkdr}rF*fCl78?&UE}#=y{xVRt+eASo^lxC6L0@>_zT)Huw<6zVr~%VMQr7o-Pk7T_0Q!Z9n#rb#o|KTA6Rb@}L)hn393> znF%2Dz5OamTV$qR%4pJjBu=M7Pdu(2)9Y6{$7K*8s>Z<*?@hG>f=HVO-Zs1&N-wO1 zes?+e{Z$jP7_fv*D9xQVvcwh}pP2JOn)Io9h{FAPV;n`49XtQ{>Kmvk$--JWd;Abc z^+LJvVv-ps=VyLV(mzPvkN)R4;FdyI>D56_kXa&-Ty@b|GzoS+q`mlYNe{- zTJgUz+}HW=EsuX^qvMphFaGUW_)-S2B&^YBQ`N^~``zzgot1@XBNzTz3ikm`0=P7C lLuK1_{~5GKqisp>vfk|&{phYL`iFwp6*H?p2`0B6{V(8mzC!>2 literal 0 HcmV?d00001 diff --git a/docs/installation/configuration.md b/docs/installation/configuration.md index c3ed2fed..ad871757 100644 --- a/docs/installation/configuration.md +++ b/docs/installation/configuration.md @@ -21,6 +21,8 @@ key | optional | default | description *** +After adding it to the configuration you need to restart Home Assistant. + ## Github Personal Access Token _You need to generate an Access Token to your account before you start using this._ diff --git a/docs/installation/manual.md b/docs/installation/manual.md index 31f6e24d..7a829da1 100644 --- a/docs/installation/manual.md +++ b/docs/installation/manual.md @@ -73,7 +73,11 @@ If you **do not** see a `custom_components` folder in **the same** folder as `co The `custom_components` **needs** to be in **the exact same** folder as `configuration.yaml` -### Step 7 - ✏️ +### Step 7 - Restart Home Assistant + +Restart Home Assistant once before moving on to step 8. + +### Step 8 - ✏️ [You should now be done, next part will be to add it to your configuration.](../configuration) diff --git a/docs/installation/terminal.md b/docs/installation/terminal.md index 9319f1fa..bba74bfc 100644 --- a/docs/installation/terminal.md +++ b/docs/installation/terminal.md @@ -11,12 +11,14 @@ Then run the following commands: ```bash git clone https://github.com/custom-components/hacs.git hacs_temp cd hacs_temp -git checkout $(git describe --tags $(git rev-list --tags --max-count=1)) +git checkout $(git describe --tags --always $(git rev-list --tags --max-count=1000) | grep -e "[0-9]\+\.[0-9]\+\.[0-9]\+$" | head -n 1) cd ../ cp -r hacs_temp/custom_components/hacs hacs rm -R hacs_temp ``` +Restart Home Assistant once before moving on to the configuration. + [You should now be done, next part will be to add it to your configuration.](../configuration) diff --git a/info.md b/info.md index f5196b62..1c611ec0 100644 --- a/info.md +++ b/info.md @@ -1,10 +1,14 @@ +# PRE RELEASE (BETA)! + +# [pre-release documentation](https://custom-components.github.io/hacs/next/) + ![gif](https://custom-components.github.io/hacs/images/hacsdemo.gif) ## Highlights of what HACS can do -- Help you discover new integrations, plugins and AppDaemon apps. -- Help you install (download) new integrations, plugins and AppDaemon apps. -- Help you keep track of your integrations, plugins and AppDaemon apps. +- Help you discover new custom elements. +- Help you install (download) new custom elements. +- Help you keep track of your custom elements. - Manage(Install/Upgrade/Remove) - Shortcuts to repositories/issue tracker diff --git a/requirements.txt b/requirements.txt index e6365b97..dc0a81b8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,5 @@ aiofiles==0.4.0 -backoff==1.8.0 \ No newline at end of file +backoff==1.8.0 +aresponses==1.1.1 +pytest==4.6.3 +packaging==19.0 \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..8d16f202 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Initialize.""" diff --git a/tests/data/__init__.py b/tests/data/__init__.py new file mode 100644 index 00000000..8d16f202 --- /dev/null +++ b/tests/data/__init__.py @@ -0,0 +1 @@ +"""Initialize.""" diff --git a/tests/data/aiogithub.py b/tests/data/aiogithub.py new file mode 100644 index 00000000..0570c863 --- /dev/null +++ b/tests/data/aiogithub.py @@ -0,0 +1,16 @@ +"""Test data for AIOGitHub.""" +# pylint: disable=invalid-name +import pytest + + +@pytest.fixture() +def response_get_repo_awesome(): + return { + "id": 99999999, + "full_name": "awesome-dev/awesome-repo", + "pushed_at": "1970-01-01T00:00:00Z", + "archived": False, + "description": "Awesome!", + "topics": ["awesome"], + "default_branch": "master", + } diff --git a/tests/test_aiogithub.py b/tests/test_aiogithub.py new file mode 100644 index 00000000..0c2fb9de --- /dev/null +++ b/tests/test_aiogithub.py @@ -0,0 +1,31 @@ +"""Tests for AIOGitHub.""" + +import json + +import aiohttp +import pytest + +from custom_components.hacs.aiogithub import AIOGitHub, AIOGithubRepository +from .data.aiogithub import response_get_repo_awesome + +TOKEN = "xxx" +HEADERS = {"Content-Type": "application/json"} + + +@pytest.mark.asyncio +async def test_get_repo_awesome(aresponses, event_loop, response_get_repo_awesome): + """Test AIOGitHub.get_repo("awesome-dev/awesome-repo").""" + aresponses.add( + "api.github.com", + "/repos/awesome-dev/awesome-repo", + "GET", + aresponses.Response( + text=json.dumps(response_get_repo_awesome), status=200, headers=HEADERS + ), + ) + + async with aiohttp.ClientSession(loop=event_loop) as session: + github = AIOGitHub(TOKEN, event_loop, session) + repository = await github.get_repo("awesome-dev/awesome-repo") + assert isinstance(repository, AIOGithubRepository) + assert repository.id == 99999999