Coder Social home page Coder Social logo

pyhaversion's Introduction

pyhaversion

codecov python version PyPI Actions

Get the latest Home Assistant version from various sources.

Installation

python3 -m pip install pyhaversion

Look at the file example.py for a usage example.

Contribute

All contributions are welcome!

  1. Fork the repository
  2. Clone the repository locally and open the devcontainer or use GitHub codespaces
  3. Do your changes
  4. Lint the files with make black
  5. Ensure all tests passes with make test
  6. Ensure 100% coverage with make coverage
  7. Commit your work, and push it to GitHub
  8. Create a PR against the main branch

pyhaversion's People

Contributors

agners avatar dependabot[bot] avatar fabaff avatar frenck avatar ludeeus avatar makefu avatar onkelbeh avatar sennevds avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

pyhaversion's Issues

PyPiVersion returns the wrong version for "beta" branch

Is your feature request related to a problem? Please describe.
PyPiVersion is returning the wrong version number (99.3) for the "beta" branch.
I'd reported this over at the Home Assistant repo (home-assistant/core#32268) but the issue appears to be with this package.

Describe the solution you'd like
Should return the latest beta version (which was 0.106.b5 at the time I tested this).

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Additional context
Add any other context or screenshots about the feature request here.

Appears to be due to using the built-in sorted function to sort the releases by version in descending order and, with a simple alphanumeric sort, 0.99.3 is treated as the latest version.

It looks like this can be fixed by using the natsorted function from the natsort package instead which applied a "natural" sorting and seems to pick up the correct version.

PyPiVersion wrong beta versions if there are more than 10 beta releases

Describe the bug

When using the "beta" branch for PyPi, if the beta has more than 10 releases, for example 0.115 which is currently at 0.115.0b11, the wrong version is returned (in this case 0.115.0b9). It would appear that the beta part of the version number is being sorted alphanumerically resulting in 0.115.0b11 (and 0.115.0b10) coming before 0.115.0b9

log

Add your logs here.

Series version tags resulted in less useful sensor data

The problem

Prior to 2021.7 and the introduction of series version tags container versions would appear with full patch number, eg 2021.6.6. Now the container version sensor shows only 2021.7, with no indication that there is a new patch release.

For me this has broken some automation around when a new release comes out. I am interested to hear others thoughts on the new behavior.

Environment

  • Operating system:
  • Python version:

Problem-relevant code

Traceback/Error logs

2021-07-12 15:35:11 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event state_changed[L]: entity_id=sensor.ha_docker_version_stable, old_state=None, new_state=<state sensor.ha_docker_version_stable=2021.7; source=HaVersionSource.CONTAINER, channel=HaVersionChannel.STABLE, friendly_name=Latest Docker, icon=mdi:docker @ 2021-07-12T15:35:11.983537-05:00>>
2021-07-12 15:35:12 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event state_changed[L]: entity_id=sensor.ha_github_version, old_state=None, new_state=<state sensor.ha_github_version=2021.7.1; friendly_name=Latest Github, icon=mdi:github @ 2021-07-12T15:35:12.325471-05:00>>

Additional information

Docker version

Describe the bug
It seems that with a recent change (maybe home-assistant/core#26085), the Docker versioning schema has changed a little bit and pyhaversion is always returning the details for dev.

Docker beta

Version: 0.98.0.dev20190827
Attributes: {'beta': True, 'source': 'Docker', 'image': 'qemux86-64-homeassistant'}

Docker stable

Version: 0.98.0.dev20190827
Attributes: {'beta': False, 'source': 'Docker', 'image': 'qemux86-64-homeassistant'}

See also: home-assistant/core#26213

HAIO option doesn't work

Describe the bug
I tried to use the version sensor in home assistant with source set to haio and I received this message in my logs:
log

2020-06-08 12:50:48 CRITICAL (MainThread) [pyhaversion] Something really wrong happened! - Expecting value: line 1 column 1 (char 0)

I looked in the source here and it seems when trying to use pyhaversion with source of haio it tries to use the URL https://www.home-assistant.io/version.json to get the version info. This URL does not return JSON though, it returns HTML with JSON inside it.

I'm not sure what broke, seems like either the website needs to be fixed so it serves up JSON again, the library needs to change the URL it uses or the haio option needs to be removed.

test_stable_version and test_etag fail on python 3.11

The problem

The test suite fails two tests on python 3.11:

FAILED tests/test_supervisor.py::test_stable_version - pyhaversion.exceptions.HaVersionFetchException: Error fetching version information from HaVersionSource.SUPERVISOR Server disconnected
FAILED tests/test_supervisor.py::test_etag - pyhaversion.exceptions.HaVersionFetchException: Error fetching version information from HaVersionSource.SUPERVISOR Server disconnected

Environment

  • Operating system: Linux
  • Python version: 3.11.3

Problem-relevant code

Running pytest against python 3.11 should surface the issue.

Traceback/Error logs

============================================ test session starts =============================================
platform linux -- Python 3.11.3, pytest-7.3.1, pluggy-1.0.0
rootdir: /tmp/pyhaversion
plugins: asyncio-0.21.0, aresponses-2.1.6
asyncio: mode=Mode.STRICT
collected 27 items                                                                                           

tests/test_container.py ..........                                                                     [ 37%]
tests/test_haio.py ...                                                                                 [ 48%]
tests/test_local.py .                                                                                  [ 51%]
tests/test_pypi.py .....                                                                               [ 70%]
tests/test_supervisor.py F..F                                                                          [ 85%]
tests/test_version.py ....                                                                             [100%]

================================================== FAILURES ==================================================
____________________________________________ test_stable_version _____________________________________________

self = <pyhaversion.version.HaVersion object at 0x7f07f315e2d0>

    async def get_version(
        self,
        *,
        etag: str | None = None,
    ) -> tuple[AwesomeVersion, dict[str, Any]]:
        """
        Get version update.
    
        Returns a tupe with version, version_data.
        """
        try:
>           await self._handler.fetch(etag=etag)

pyhaversion/version.py:89: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = HaVersionSupervisor(source=<HaVersionSource.SUPERVISOR: 'supervisor'>, channel=<HaVersionChannel.STABLE: 'stable'>, board='ova', image='default', _etag=None)
kwargs = {'etag': None}
headers = {'Content-Type': 'application/json', 'If-None-Match': 'W/"test"', 'User-Agent': 'python/pyhaversion'}
etag = None

    async def fetch(self, **kwargs):
        """Logic to fetch new version data."""
        headers = DEFAULT_HEADERS
        if (etag := kwargs.get("etag")) is not None:
            headers[IF_NONE_MATCH] = etag
    
>       request = await self.session.get(
            url=URL.format(channel=self.channel),
            headers=headers,
            timeout=ClientTimeout(total=self.timeout),
        )

pyhaversion/supervisor.py:47: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <aiohttp.client.ClientSession object at 0x7f07f315ed50>, method = 'GET'
str_or_url = 'https://version.home-assistant.io/HaVersionChannel.STABLE.json'

    async def _request(
        self,
        method: str,
        str_or_url: StrOrURL,
        *,
        params: Optional[Mapping[str, str]] = None,
        data: Any = None,
        json: Any = None,
        cookies: Optional[LooseCookies] = None,
        headers: Optional[LooseHeaders] = None,
        skip_auto_headers: Optional[Iterable[str]] = None,
        auth: Optional[BasicAuth] = None,
        allow_redirects: bool = True,
        max_redirects: int = 10,
        compress: Optional[str] = None,
        chunked: Optional[bool] = None,
        expect100: bool = False,
        raise_for_status: Optional[bool] = None,
        read_until_eof: bool = True,
        proxy: Optional[StrOrURL] = None,
        proxy_auth: Optional[BasicAuth] = None,
        timeout: Union[ClientTimeout, object] = sentinel,
        verify_ssl: Optional[bool] = None,
        fingerprint: Optional[bytes] = None,
        ssl_context: Optional[SSLContext] = None,
        ssl: Optional[Union[SSLContext, bool, Fingerprint]] = None,
        proxy_headers: Optional[LooseHeaders] = None,
        trace_request_ctx: Optional[SimpleNamespace] = None,
        read_bufsize: Optional[int] = None,
    ) -> ClientResponse:
    
        # NOTE: timeout clamps existing connect and read timeouts.  We cannot
        # set the default to None because we need to detect if the user wants
        # to use the existing timeouts by setting timeout to None.
    
        if self.closed:
            raise RuntimeError("Session is closed")
    
        ssl = _merge_ssl_params(ssl, verify_ssl, ssl_context, fingerprint)
    
        if data is not None and json is not None:
            raise ValueError(
                "data and json parameters can not be used at the same time"
            )
        elif json is not None:
            data = payload.JsonPayload(json, dumps=self._json_serialize)
    
        if not isinstance(chunked, bool) and chunked is not None:
            warnings.warn("Chunk size is deprecated #1615", DeprecationWarning)
    
        redirects = 0
        history = []
        version = self._version
    
        # Merge with default headers and transform to CIMultiDict
        headers = self._prepare_headers(headers)
        proxy_headers = self._prepare_headers(proxy_headers)
    
        try:
            url = self._build_url(str_or_url)
        except ValueError as e:
            raise InvalidURL(str_or_url) from e
    
        skip_headers = set(self._skip_auto_headers)
        if skip_auto_headers is not None:
            for i in skip_auto_headers:
                skip_headers.add(istr(i))
    
        if proxy is not None:
            try:
                proxy = URL(proxy)
            except ValueError as e:
                raise InvalidURL(proxy) from e
    
        if timeout is sentinel:
            real_timeout: ClientTimeout = self._timeout
        else:
            if not isinstance(timeout, ClientTimeout):
                real_timeout = ClientTimeout(total=timeout)  # type: ignore[arg-type]
            else:
                real_timeout = timeout
        # timeout is cumulative for all request operations
        # (request, redirects, responses, data consuming)
        tm = TimeoutHandle(self._loop, real_timeout.total)
        handle = tm.start()
    
        if read_bufsize is None:
            read_bufsize = self._read_bufsize
    
        traces = [
            Trace(
                self,
                trace_config,
                trace_config.trace_config_ctx(trace_request_ctx=trace_request_ctx),
            )
            for trace_config in self._trace_configs
        ]
    
        for trace in traces:
            await trace.send_request_start(method, url.update_query(params), headers)
    
        timer = tm.timer()
        try:
            with timer:
                while True:
                    url, auth_from_url = strip_auth_from_url(url)
                    if auth and auth_from_url:
                        raise ValueError(
                            "Cannot combine AUTH argument with "
                            "credentials encoded in URL"
                        )
    
                    if auth is None:
                        auth = auth_from_url
                    if auth is None:
                        auth = self._default_auth
                    # It would be confusing if we support explicit
                    # Authorization header with auth argument
                    if (
                        headers is not None
                        and auth is not None
                        and hdrs.AUTHORIZATION in headers
                    ):
                        raise ValueError(
                            "Cannot combine AUTHORIZATION header "
                            "with AUTH argument or credentials "
                            "encoded in URL"
                        )
    
                    all_cookies = self._cookie_jar.filter_cookies(url)
    
                    if cookies is not None:
                        tmp_cookie_jar = CookieJar()
                        tmp_cookie_jar.update_cookies(cookies)
                        req_cookies = tmp_cookie_jar.filter_cookies(url)
                        if req_cookies:
                            all_cookies.load(req_cookies)
    
                    if proxy is not None:
                        proxy = URL(proxy)
                    elif self._trust_env:
                        with suppress(LookupError):
                            proxy, proxy_auth = get_env_proxy_for_url(url)
    
                    req = self._request_class(
                        method,
                        url,
                        params=params,
                        headers=headers,
                        skip_auto_headers=skip_headers,
                        data=data,
                        cookies=all_cookies,
                        auth=auth,
                        version=version,
                        compress=compress,
                        chunked=chunked,
                        expect100=expect100,
                        loop=self._loop,
                        response_class=self._response_class,
                        proxy=proxy,
                        proxy_auth=proxy_auth,
                        timer=timer,
                        session=self,
                        ssl=ssl,
                        proxy_headers=proxy_headers,
                        traces=traces,
                    )
    
                    # connection timeout
                    try:
                        async with ceil_timeout(real_timeout.connect):
                            assert self._connector is not None
                            conn = await self._connector.connect(
                                req, traces=traces, timeout=real_timeout
                            )
                    except asyncio.TimeoutError as exc:
                        raise ServerTimeoutError(
                            "Connection timeout " "to host {}".format(url)
                        ) from exc
    
                    assert conn.transport is not None
    
                    assert conn.protocol is not None
                    conn.protocol.set_response_params(
                        timer=timer,
                        skip_payload=method.upper() == "HEAD",
                        read_until_eof=read_until_eof,
                        auto_decompress=self._auto_decompress,
                        read_timeout=real_timeout.sock_read,
                        read_bufsize=read_bufsize,
                    )
    
                    try:
                        try:
                            resp = await req.send(conn)
                            try:
>                               await resp.start(conn)

.venv/lib/python3.11/site-packages/aiohttp-3.8.4-py3.11-linux-x86_64.egg/aiohttp/client.py:560: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <ClientResponse(https://version.home-assistant.io/HaVersionChannel.STABLE.json) [None None]>
None

connection = Connection<ConnectionKey(host='version.home-assistant.io', port=443, is_ssl=False, ssl=None, proxy=None, proxy_auth=None, proxy_headers_hash=None)>

    async def start(self, connection: "Connection") -> "ClientResponse":
        """Start response processing."""
        self._closed = False
        self._protocol = connection.protocol
        self._connection = connection
    
        with self._timer:
            while True:
                # read response
                try:
                    protocol = self._protocol
>                   message, payload = await protocol.read()  # type: ignore[union-attr]

.venv/lib/python3.11/site-packages/aiohttp-3.8.4-py3.11-linux-x86_64.egg/aiohttp/client_reqrep.py:899: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <aiohttp.client_proto.ResponseHandler object at 0x7f07f311fc40>

    async def read(self) -> _T:
        if not self._buffer and not self._eof:
            assert not self._waiter
            self._waiter = self._loop.create_future()
            try:
>               await self._waiter
E               aiohttp.client_exceptions.ServerDisconnectedError: Server disconnected

.venv/lib/python3.11/site-packages/aiohttp-3.8.4-py3.11-linux-x86_64.egg/aiohttp/streams.py:616: ServerDisconnectedError

The above exception was the direct cause of the following exception:

aresponses = <aresponses.main.ResponsesMockServer object at 0x7f07f315d590>

    @pytest.mark.asyncio
    async def test_stable_version(aresponses):
        """Test hassio stable."""
        aresponses.add(
            "version.home-assistant.io",
            "/stable.json",
            "get",
            aresponses.Response(
                text=fixture("supervisor/default", False), status=200, headers=HEADERS
            ),
        )
        async with aiohttp.ClientSession() as session:
            haversion = HaVersion(session=session, source=HaVersionSource.SUPERVISOR)
>           await haversion.get_version()

tests/test_supervisor.py:32: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <pyhaversion.version.HaVersion object at 0x7f07f315e2d0>

    async def get_version(
        self,
        *,
        etag: str | None = None,
    ) -> tuple[AwesomeVersion, dict[str, Any]]:
        """
        Get version update.
    
        Returns a tupe with version, version_data.
        """
        try:
            await self._handler.fetch(etag=etag)
    
        except asyncio.TimeoutError as exception:
            raise HaVersionFetchException(
                f"Timeout of {self._handler.timeout} seconds was "
                f"reached while fetching version for {self.source}"
            ) from exception
    
        except (ClientError, gaierror) as exception:
>           raise HaVersionFetchException(
                f"Error fetching version information from {self.source} {exception}"
            ) from exception
E           pyhaversion.exceptions.HaVersionFetchException: Error fetching version information from HaVersionSource.SUPERVISOR Server disconnected

pyhaversion/version.py:98: HaVersionFetchException
------------------------------------------- Captured stderr setup --------------------------------------------
DEBUG:asyncio:Using selector: EpollSelector
--------------------------------------------- Captured log setup ---------------------------------------------
DEBUG    asyncio:selector_events.py:54 Using selector: EpollSelector
-------------------------------------------- Captured stderr call --------------------------------------------
ERROR:aiohttp.server:Unhandled runtime exception
Traceback (most recent call last):
  File "/tmp/pyhaversion/.venv/lib/python3.11/site-packages/aiohttp-3.8.4-py3.11-linux-x86_64.egg/aiohttp/web_protocol.py", line 600, in finish_response
    prepare_meth = resp.prepare
                   ^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'prepare'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/tmp/pyhaversion/.venv/lib/python3.11/site-packages/aiohttp-3.8.4-py3.11-linux-x86_64.egg/aiohttp/web_protocol.py", line 512, in start
    resp, reset = await task
                  ^^^^^^^^^^
  File "/tmp/pyhaversion/.venv/lib/python3.11/site-packages/aiohttp-3.8.4-py3.11-linux-x86_64.egg/aiohttp/web_protocol.py", line 458, in _handle_request
    reset = await self.finish_response(request, resp, start_time)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/pyhaversion/.venv/lib/python3.11/site-packages/aiohttp-3.8.4-py3.11-linux-x86_64.egg/aiohttp/web_protocol.py", line 603, in finish_response
    raise RuntimeError("Missing return " "statement on request handler")
RuntimeError: Missing return statement on request handler
--------------------------------------------- Captured log call ----------------------------------------------
ERROR    aiohttp.server:web_protocol.py:403 Unhandled runtime exception
Traceback (most recent call last):
  File "/tmp/pyhaversion/.venv/lib/python3.11/site-packages/aiohttp-3.8.4-py3.11-linux-x86_64.egg/aiohttp/web_protocol.py", line 600, in finish_response
    prepare_meth = resp.prepare
                   ^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'prepare'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/tmp/pyhaversion/.venv/lib/python3.11/site-packages/aiohttp-3.8.4-py3.11-linux-x86_64.egg/aiohttp/web_protocol.py", line 512, in start
    resp, reset = await task
                  ^^^^^^^^^^
  File "/tmp/pyhaversion/.venv/lib/python3.11/site-packages/aiohttp-3.8.4-py3.11-linux-x86_64.egg/aiohttp/web_protocol.py", line 458, in _handle_request
    reset = await self.finish_response(request, resp, start_time)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/pyhaversion/.venv/lib/python3.11/site-packages/aiohttp-3.8.4-py3.11-linux-x86_64.egg/aiohttp/web_protocol.py", line 603, in finish_response
    raise RuntimeError("Missing return " "statement on request handler")
RuntimeError: Missing return statement on request handler
------------------------------------------ Captured stderr teardown ------------------------------------------
DEBUG:asyncio:Using selector: EpollSelector
------------------------------------------- Captured log teardown --------------------------------------------
DEBUG    asyncio:selector_events.py:54 Using selector: EpollSelector
_________________________________________________ test_etag __________________________________________________

self = <pyhaversion.version.HaVersion object at 0x7f07f3115250>

    async def get_version(
        self,
        *,
        etag: str | None = None,
    ) -> tuple[AwesomeVersion, dict[str, Any]]:
        """
        Get version update.
    
        Returns a tupe with version, version_data.
        """
        try:
>           await self._handler.fetch(etag=etag)

pyhaversion/version.py:89: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = HaVersionSupervisor(source=<HaVersionSource.SUPERVISOR: 'supervisor'>, channel=<HaVersionChannel.STABLE: 'stable'>, board='ova', image='default', _etag=None)
kwargs = {'etag': None}
headers = {'Content-Type': 'application/json', 'If-None-Match': 'W/"test"', 'User-Agent': 'python/pyhaversion'}
etag = None

    async def fetch(self, **kwargs):
        """Logic to fetch new version data."""
        headers = DEFAULT_HEADERS
        if (etag := kwargs.get("etag")) is not None:
            headers[IF_NONE_MATCH] = etag
    
>       request = await self.session.get(
            url=URL.format(channel=self.channel),
            headers=headers,
            timeout=ClientTimeout(total=self.timeout),
        )

pyhaversion/supervisor.py:47: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <aiohttp.client.ClientSession object at 0x7f07f3114fd0>, method = 'GET'
str_or_url = 'https://version.home-assistant.io/HaVersionChannel.STABLE.json'

    async def _request(
        self,
        method: str,
        str_or_url: StrOrURL,
        *,
        params: Optional[Mapping[str, str]] = None,
        data: Any = None,
        json: Any = None,
        cookies: Optional[LooseCookies] = None,
        headers: Optional[LooseHeaders] = None,
        skip_auto_headers: Optional[Iterable[str]] = None,
        auth: Optional[BasicAuth] = None,
        allow_redirects: bool = True,
        max_redirects: int = 10,
        compress: Optional[str] = None,
        chunked: Optional[bool] = None,
        expect100: bool = False,
        raise_for_status: Optional[bool] = None,
        read_until_eof: bool = True,
        proxy: Optional[StrOrURL] = None,
        proxy_auth: Optional[BasicAuth] = None,
        timeout: Union[ClientTimeout, object] = sentinel,
        verify_ssl: Optional[bool] = None,
        fingerprint: Optional[bytes] = None,
        ssl_context: Optional[SSLContext] = None,
        ssl: Optional[Union[SSLContext, bool, Fingerprint]] = None,
        proxy_headers: Optional[LooseHeaders] = None,
        trace_request_ctx: Optional[SimpleNamespace] = None,
        read_bufsize: Optional[int] = None,
    ) -> ClientResponse:
    
        # NOTE: timeout clamps existing connect and read timeouts.  We cannot
        # set the default to None because we need to detect if the user wants
        # to use the existing timeouts by setting timeout to None.
    
        if self.closed:
            raise RuntimeError("Session is closed")
    
        ssl = _merge_ssl_params(ssl, verify_ssl, ssl_context, fingerprint)
    
        if data is not None and json is not None:
            raise ValueError(
                "data and json parameters can not be used at the same time"
            )
        elif json is not None:
            data = payload.JsonPayload(json, dumps=self._json_serialize)
    
        if not isinstance(chunked, bool) and chunked is not None:
            warnings.warn("Chunk size is deprecated #1615", DeprecationWarning)
    
        redirects = 0
        history = []
        version = self._version
    
        # Merge with default headers and transform to CIMultiDict
        headers = self._prepare_headers(headers)
        proxy_headers = self._prepare_headers(proxy_headers)
    
        try:
            url = self._build_url(str_or_url)
        except ValueError as e:
            raise InvalidURL(str_or_url) from e
    
        skip_headers = set(self._skip_auto_headers)
        if skip_auto_headers is not None:
            for i in skip_auto_headers:
                skip_headers.add(istr(i))
    
        if proxy is not None:
            try:
                proxy = URL(proxy)
            except ValueError as e:
                raise InvalidURL(proxy) from e
    
        if timeout is sentinel:
            real_timeout: ClientTimeout = self._timeout
        else:
            if not isinstance(timeout, ClientTimeout):
                real_timeout = ClientTimeout(total=timeout)  # type: ignore[arg-type]
            else:
                real_timeout = timeout
        # timeout is cumulative for all request operations
        # (request, redirects, responses, data consuming)
        tm = TimeoutHandle(self._loop, real_timeout.total)
        handle = tm.start()
    
        if read_bufsize is None:
            read_bufsize = self._read_bufsize
    
        traces = [
            Trace(
                self,
                trace_config,
                trace_config.trace_config_ctx(trace_request_ctx=trace_request_ctx),
            )
            for trace_config in self._trace_configs
        ]
    
        for trace in traces:
            await trace.send_request_start(method, url.update_query(params), headers)
    
        timer = tm.timer()
        try:
            with timer:
                while True:
                    url, auth_from_url = strip_auth_from_url(url)
                    if auth and auth_from_url:
                        raise ValueError(
                            "Cannot combine AUTH argument with "
                            "credentials encoded in URL"
                        )
    
                    if auth is None:
                        auth = auth_from_url
                    if auth is None:
                        auth = self._default_auth
                    # It would be confusing if we support explicit
                    # Authorization header with auth argument
                    if (
                        headers is not None
                        and auth is not None
                        and hdrs.AUTHORIZATION in headers
                    ):
                        raise ValueError(
                            "Cannot combine AUTHORIZATION header "
                            "with AUTH argument or credentials "
                            "encoded in URL"
                        )
    
                    all_cookies = self._cookie_jar.filter_cookies(url)
    
                    if cookies is not None:
                        tmp_cookie_jar = CookieJar()
                        tmp_cookie_jar.update_cookies(cookies)
                        req_cookies = tmp_cookie_jar.filter_cookies(url)
                        if req_cookies:
                            all_cookies.load(req_cookies)
    
                    if proxy is not None:
                        proxy = URL(proxy)
                    elif self._trust_env:
                        with suppress(LookupError):
                            proxy, proxy_auth = get_env_proxy_for_url(url)
    
                    req = self._request_class(
                        method,
                        url,
                        params=params,
                        headers=headers,
                        skip_auto_headers=skip_headers,
                        data=data,
                        cookies=all_cookies,
                        auth=auth,
                        version=version,
                        compress=compress,
                        chunked=chunked,
                        expect100=expect100,
                        loop=self._loop,
                        response_class=self._response_class,
                        proxy=proxy,
                        proxy_auth=proxy_auth,
                        timer=timer,
                        session=self,
                        ssl=ssl,
                        proxy_headers=proxy_headers,
                        traces=traces,
                    )
    
                    # connection timeout
                    try:
                        async with ceil_timeout(real_timeout.connect):
                            assert self._connector is not None
                            conn = await self._connector.connect(
                                req, traces=traces, timeout=real_timeout
                            )
                    except asyncio.TimeoutError as exc:
                        raise ServerTimeoutError(
                            "Connection timeout " "to host {}".format(url)
                        ) from exc
    
                    assert conn.transport is not None
    
                    assert conn.protocol is not None
                    conn.protocol.set_response_params(
                        timer=timer,
                        skip_payload=method.upper() == "HEAD",
                        read_until_eof=read_until_eof,
                        auto_decompress=self._auto_decompress,
                        read_timeout=real_timeout.sock_read,
                        read_bufsize=read_bufsize,
                    )
    
                    try:
                        try:
                            resp = await req.send(conn)
                            try:
>                               await resp.start(conn)

.venv/lib/python3.11/site-packages/aiohttp-3.8.4-py3.11-linux-x86_64.egg/aiohttp/client.py:560: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <ClientResponse(https://version.home-assistant.io/HaVersionChannel.STABLE.json) [None None]>
None

connection = Connection<ConnectionKey(host='version.home-assistant.io', port=443, is_ssl=False, ssl=None, proxy=None, proxy_auth=None, proxy_headers_hash=None)>

    async def start(self, connection: "Connection") -> "ClientResponse":
        """Start response processing."""
        self._closed = False
        self._protocol = connection.protocol
        self._connection = connection
    
        with self._timer:
            while True:
                # read response
                try:
                    protocol = self._protocol
>                   message, payload = await protocol.read()  # type: ignore[union-attr]

.venv/lib/python3.11/site-packages/aiohttp-3.8.4-py3.11-linux-x86_64.egg/aiohttp/client_reqrep.py:899: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <aiohttp.client_proto.ResponseHandler object at 0x7f07f2ed1be0>

    async def read(self) -> _T:
        if not self._buffer and not self._eof:
            assert not self._waiter
            self._waiter = self._loop.create_future()
            try:
>               await self._waiter
E               aiohttp.client_exceptions.ServerDisconnectedError: Server disconnected

.venv/lib/python3.11/site-packages/aiohttp-3.8.4-py3.11-linux-x86_64.egg/aiohttp/streams.py:616: ServerDisconnectedError

The above exception was the direct cause of the following exception:

aresponses = <aresponses.main.ResponsesMockServer object at 0x7f07f30c1350>

    @pytest.mark.asyncio
    async def test_etag(aresponses):
        """Test hassio etag."""
        aresponses.add(
            "version.home-assistant.io",
            "/stable.json",
            "get",
            aresponses.Response(
                text=fixture("supervisor/default", False),
                status=200,
                headers={**HEADERS, "Etag": "test"},
            ),
        )
        aresponses.add(
            "version.home-assistant.io",
            "/stable.json",
            "get",
            aresponses.Response(status=304, headers=HEADERS),
        )
        async with aiohttp.ClientSession() as session:
            haversion = HaVersion(session=session, source=HaVersionSource.SUPERVISOR)
>           await haversion.get_version(etag=haversion.etag)

tests/test_supervisor.py:83: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <pyhaversion.version.HaVersion object at 0x7f07f3115250>

    async def get_version(
        self,
        *,
        etag: str | None = None,
    ) -> tuple[AwesomeVersion, dict[str, Any]]:
        """
        Get version update.
    
        Returns a tupe with version, version_data.
        """
        try:
            await self._handler.fetch(etag=etag)
    
        except asyncio.TimeoutError as exception:
            raise HaVersionFetchException(
                f"Timeout of {self._handler.timeout} seconds was "
                f"reached while fetching version for {self.source}"
            ) from exception
    
        except (ClientError, gaierror) as exception:
>           raise HaVersionFetchException(
                f"Error fetching version information from {self.source} {exception}"
            ) from exception
E           pyhaversion.exceptions.HaVersionFetchException: Error fetching version information from HaVersionSource.SUPERVISOR Server disconnected

pyhaversion/version.py:98: HaVersionFetchException
------------------------------------------- Captured stderr setup --------------------------------------------
DEBUG:asyncio:Using selector: EpollSelector
--------------------------------------------- Captured log setup ---------------------------------------------
DEBUG    asyncio:selector_events.py:54 Using selector: EpollSelector
-------------------------------------------- Captured stderr call --------------------------------------------
ERROR:aiohttp.server:Unhandled runtime exception
Traceback (most recent call last):
  File "/tmp/pyhaversion/.venv/lib/python3.11/site-packages/aiohttp-3.8.4-py3.11-linux-x86_64.egg/aiohttp/web_protocol.py", line 600, in finish_response
    prepare_meth = resp.prepare
                   ^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'prepare'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/tmp/pyhaversion/.venv/lib/python3.11/site-packages/aiohttp-3.8.4-py3.11-linux-x86_64.egg/aiohttp/web_protocol.py", line 512, in start
    resp, reset = await task
                  ^^^^^^^^^^
  File "/tmp/pyhaversion/.venv/lib/python3.11/site-packages/aiohttp-3.8.4-py3.11-linux-x86_64.egg/aiohttp/web_protocol.py", line 458, in _handle_request
    reset = await self.finish_response(request, resp, start_time)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/pyhaversion/.venv/lib/python3.11/site-packages/aiohttp-3.8.4-py3.11-linux-x86_64.egg/aiohttp/web_protocol.py", line 603, in finish_response
    raise RuntimeError("Missing return " "statement on request handler")
RuntimeError: Missing return statement on request handler
--------------------------------------------- Captured log call ----------------------------------------------
ERROR    aiohttp.server:web_protocol.py:403 Unhandled runtime exception
Traceback (most recent call last):
  File "/tmp/pyhaversion/.venv/lib/python3.11/site-packages/aiohttp-3.8.4-py3.11-linux-x86_64.egg/aiohttp/web_protocol.py", line 600, in finish_response
    prepare_meth = resp.prepare
                   ^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'prepare'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/tmp/pyhaversion/.venv/lib/python3.11/site-packages/aiohttp-3.8.4-py3.11-linux-x86_64.egg/aiohttp/web_protocol.py", line 512, in start
    resp, reset = await task
                  ^^^^^^^^^^
  File "/tmp/pyhaversion/.venv/lib/python3.11/site-packages/aiohttp-3.8.4-py3.11-linux-x86_64.egg/aiohttp/web_protocol.py", line 458, in _handle_request
    reset = await self.finish_response(request, resp, start_time)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/pyhaversion/.venv/lib/python3.11/site-packages/aiohttp-3.8.4-py3.11-linux-x86_64.egg/aiohttp/web_protocol.py", line 603, in finish_response
    raise RuntimeError("Missing return " "statement on request handler")
RuntimeError: Missing return statement on request handler
------------------------------------------ Captured stderr teardown ------------------------------------------
DEBUG:asyncio:Using selector: EpollSelector
------------------------------------------- Captured log teardown --------------------------------------------
DEBUG    asyncio:selector_events.py:54 Using selector: EpollSelector
========================================== short test summary info ===========================================
FAILED tests/test_supervisor.py::test_stable_version - pyhaversion.exceptions.HaVersionFetchException: Error fetching version information from HaVersionSource.S...
FAILED tests/test_supervisor.py::test_etag - pyhaversion.exceptions.HaVersionFetchException: Error fetching version information from HaVersionSource.S...
======================================== 2 failed, 25 passed in 0.16s ========================================

Additional information

Add support for querying the ghcr.io version

The idea

Currently there is support for querying the Docker Hub version, but according to the hass website, the preferred way to get the docker images is from ghcr.io (see the first entry under "Breaking Changes" in the 2023.7 release notes). It would be ideal if I can track that version, rather than the one on Docker Hub.

Implementation

I haven't read the code, but I imagine it would be a clone of the existing Docker Hub code but with a different url.

Alternatives

The Docker Hub version is an ok proxy for now, but I'd rather not mix-and-match sources if I can help it.

Additional context

pyhaversion installs 'tests' package which is forbidden

Hi,

first I hope you don't mind that I use your integration in Home Assistant Gentoo Overlay, find your Ebuilds are in: https://github.com/onkelbeh/HomeAssistantRepository/tree/master/dev-python/pyhaversion

Describe the bug
During some compile tests I saw that pyhaversion tries to install 'test' package at top level, this is fobidden in Gentoo (and most other Distri's). This could easily be fixed with a small change to setup.py:

For now, I added a small generic sed script to my ebuilds:

src_prepare() {
    sed "s/packages=find_packages()/packages=find_packages(exclude=['tests','tests.*'])/g" -i setup.py || die
    eapply_user
}

I fixed this in my repo a few days ago, but I have a reminder for cleanup which just popped up again.
I'll send a pull request in a few seconds.

Thanks a lot.
Please drop me a not when it's fixed, so I can remove the patch.

\B.

log
please see https://git.edevau.net/onkelbeh/HomeAssistantRepository/issues/179 for the build log.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.