PyHolfuy 0.1__tar.gz → 0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyholfuy
3
- Version: 0.1
3
+ Version: 0.3
4
4
  Summary: A Python library for interacting with Holfuy weather stations.
5
5
  Author-email: Håvard Dagenborg <haavardj@dagenborg.net>
6
6
  License-Expression: MIT
@@ -16,15 +16,7 @@ Provides-Extra: test
16
16
  Requires-Dist: aioresponses==0.7.8; extra == "test"
17
17
  Requires-Dist: coverage==7.8.0; extra == "test"
18
18
  Requires-Dist: mypy==1.15.0; extra == "test"
19
- Requires-Dist: pytest-asyncio==0.26.0; extra == "test"
20
- Requires-Dist: pytest-cov==6.1.1; extra == "test"
21
- Requires-Dist: pytest-error-for-skips==2.0.2; extra == "test"
22
- Requires-Dist: pytest-timeout==2.3.1; extra == "test"
23
- Requires-Dist: pytest==8.3.5; extra == "test"
24
- Requires-Dist: ruff==0.11.7; extra == "test"
25
- Requires-Dist: syrupy==4.9.1; extra == "test"
26
19
  Provides-Extra: dev
27
- Requires-Dist: pre-commit==4.2.0; extra == "dev"
28
20
  Dynamic: license-file
29
21
 
30
22
  ![Holfy Logo](https://holfuy.com/image/logo/holfuy-logo.png)
@@ -34,7 +26,7 @@ A Python library for talking to [Holfuy Weather Stations](https://holfuy.com/).
34
26
 
35
27
  You need an API key to access the service. To obtain one, please visit the [Holfuy API](https://api.holfuy.com/live/) pages.
36
28
 
37
- The Library only access live data.
29
+ The library can only access live data.
38
30
 
39
31
  ## Example Code
40
32
  Here is an example of how to use the library.
@@ -46,10 +38,22 @@ from holfuy import HolfuyService
46
38
 
47
39
  async def main():
48
40
  async with aiohttp.ClientSession() as session:
49
- s = HolfuyService("", session)
50
- data = await s.fetch_data(["101"])
51
- print(data)
41
+ s = HolfuyService("", session)
42
+ data = await s.fetch_data(["101"])
43
+ print(data)
52
44
 
53
45
  if __name__ == "__main__":
54
46
  asyncio.run(main())
55
47
  ```
48
+
49
+ ## Return value and errors
50
+
51
+ `fetch_data()` returns `{"measurements": [...]}` with one entry per station, also when
52
+ a single station is requested. A station the key cannot read is still listed, with
53
+ `error` and `errorCode` instead of data.
54
+
55
+ It raises, all subclasses of `holfuy.HolfuyError`:
56
+
57
+ - `HolfuyAuthError`: the API key is rejected, or it has no access to any requested station.
58
+ - `HolfuyConnectionError`: network error, timeout, or an HTTP error status.
59
+ - `HolfuyError`: any other unexpected reply.
pyholfuy-0.3/README.md ADDED
@@ -0,0 +1,38 @@
1
+ ![Holfy Logo](https://holfuy.com/image/logo/holfuy-logo.png)
2
+
3
+ # PyHolfuy
4
+ A Python library for talking to [Holfuy Weather Stations](https://holfuy.com/).
5
+
6
+ You need an API key to access the service. To obtain one, please visit the [Holfuy API](https://api.holfuy.com/live/) pages.
7
+
8
+ The library can only access live data.
9
+
10
+ ## Example Code
11
+ Here is an example of how to use the library.
12
+
13
+ ```python
14
+ import aiohttp
15
+ import asyncio
16
+ from holfuy import HolfuyService
17
+
18
+ async def main():
19
+ async with aiohttp.ClientSession() as session:
20
+ s = HolfuyService("", session)
21
+ data = await s.fetch_data(["101"])
22
+ print(data)
23
+
24
+ if __name__ == "__main__":
25
+ asyncio.run(main())
26
+ ```
27
+
28
+ ## Return value and errors
29
+
30
+ `fetch_data()` returns `{"measurements": [...]}` with one entry per station, also when
31
+ a single station is requested. A station the key cannot read is still listed, with
32
+ `error` and `errorCode` instead of data.
33
+
34
+ It raises, all subclasses of `holfuy.HolfuyError`:
35
+
36
+ - `HolfuyAuthError`: the API key is rejected, or it has no access to any requested station.
37
+ - `HolfuyConnectionError`: network error, timeout, or an HTTP error status.
38
+ - `HolfuyError`: any other unexpected reply.
@@ -0,0 +1,162 @@
1
+ """Library to handle connection with Holfuy Live API."""
2
+
3
+ import asyncio
4
+ import logging
5
+
6
+ from typing import Any
7
+
8
+ import aiohttp
9
+ from aiohttp import ClientSession
10
+
11
+ from .const import (
12
+ CONF_TEMPERATURE_UNIT_CELSIUS,
13
+ CONF_WIND_SPEED_UNIT_MS,
14
+ CONF_AVERAGING_NEWEST,
15
+ CONF_MODE_JSON,
16
+ STATIONS_ALL,
17
+ )
18
+ from .exceptions import HolfuyAuthError, HolfuyConnectionError, HolfuyError
19
+
20
+ __all__ = [
21
+ "HolfuyAuthError",
22
+ "HolfuyConnectionError",
23
+ "HolfuyError",
24
+ "HolfuyService",
25
+ ]
26
+
27
+ TIMEOUT = 30
28
+ _LOGGER = logging.getLogger(__name__)
29
+
30
+ API_URL = "https://api.holfuy.com/live/"
31
+
32
+ # noqa: E501
33
+ #### API Description
34
+ # URL Format: https://api.holfuy.com/live/?s=101&pw=pass&m=JSON&tu=C&su=m/s
35
+ #
36
+ # s: station(s) ID e.g. s=101 or you can get the data from more stations by one query as well (coma separated list) e.g. s=101,102,103 . If you would get all stations' data (to which you have access) by one single API call please use the "all" string -> s=all .
37
+ # pw: password for the API access (it could be sent by a POST "pw" parameter also)
38
+ # m:mode XML or CSV or JSON default: CSV
39
+ # avg:averaging "0": newest data from the station, "1" newest quarter hourly average, "2" newest hourly average (default = 0)
40
+ # su: wind speed unit: knots, km/h, m/s, mph, default: m/s
41
+ # tu: temperature unit C:Celsius , F: Fahrenheit default: Celsius
42
+ # batt: add this parameter to get the station's battery voltage (JSON only).
43
+ # cam: add this parameter to the URL for timestamp of the last picture from the camera. (JSON only, only for stations with a camera)
44
+ # daily: add this parameter to get daily Max-Min temperatures and precipitation since last midnight CE(S)T (JSON only, max 5 stations)
45
+ # loc: add this parameter to the URL to get the station's location in reply too (JSON only)
46
+ # utc: add this parameter to the URL for timestamps in UTC, otherwise times will be in CE(S)T
47
+ # Data: DateTime: time of the last data / average in ".date('T')." (Central European Time) or in UTC if utc param is set.
48
+ #####
49
+
50
+
51
+ class HolfuyService:
52
+ """Representation of Holfuy weather data."""
53
+
54
+ def __init__(
55
+ self,
56
+ api_key: str,
57
+ session: ClientSession,
58
+ temperature_unit:str=CONF_TEMPERATURE_UNIT_CELSIUS,
59
+ wind_speed_unit:str=CONF_WIND_SPEED_UNIT_MS,
60
+ timestamps_in_utc:bool=False,
61
+ ) -> None:
62
+ """Initialize the Weather object."""
63
+
64
+ self._api_url = API_URL
65
+ self._api_key = api_key
66
+ self._wind_speed_unit = wind_speed_unit
67
+ self._temperature_unit = temperature_unit
68
+ self._timestamps_in_utc = timestamps_in_utc
69
+ self._session = session
70
+
71
+
72
+ async def fetch_data(
73
+ self,
74
+ stations: list[str] | None = None,
75
+ averaging_mode=CONF_AVERAGING_NEWEST,
76
+ include_battery=False,
77
+ include_daily=False,
78
+ include_location=False,
79
+ ) -> dict[str, Any]:
80
+ """Get the latest data from Holfuy.
81
+
82
+ Returns {"measurements": [...]}, one entry per station. A station the key
83
+ cannot read is still listed, with "error" and "errorCode" instead of data.
84
+
85
+ Raises HolfuyAuthError if the key is rejected or can read none of the
86
+ requested stations, HolfuyConnectionError on network errors, timeouts and
87
+ HTTP errors, and HolfuyError on any other unexpected reply.
88
+ """
89
+
90
+ params = {
91
+ "pw": self._api_key,
92
+ "m": CONF_MODE_JSON,
93
+ "tu": self._temperature_unit,
94
+ "su": self._wind_speed_unit,
95
+ "avg": averaging_mode,
96
+ }
97
+
98
+ if self._timestamps_in_utc:
99
+ params["utc"] = ""
100
+
101
+ if include_battery:
102
+ params["batt"] = ""
103
+
104
+ if include_daily:
105
+ params["daily"] = ""
106
+
107
+ if include_location:
108
+ params["loc"] = ""
109
+
110
+ if stations:
111
+ params["s"] = ",".join(map(str, stations))
112
+ else:
113
+ params["s"] = STATIONS_ALL
114
+
115
+ try:
116
+ # The timeout covers the whole exchange, body included, and the
117
+ # context manager releases the connection back to the pool.
118
+ async with (
119
+ asyncio.timeout(TIMEOUT),
120
+ self._session.get(self._api_url, params=params) as resp,
121
+ ):
122
+ if resp.status >= 400:
123
+ raise HolfuyConnectionError(
124
+ f"Holfuy API returned HTTP {resp.status}"
125
+ )
126
+
127
+ # A rejected key gets HTTP 200 with the plain text "No access".
128
+ if resp.content_type != "application/json":
129
+ text = (await resp.text()).strip()
130
+ if text.lower().startswith("no access"):
131
+ raise HolfuyAuthError("Holfuy API rejected the API key")
132
+ raise HolfuyError(
133
+ f"Unexpected response from Holfuy API: {text[:100]!r}"
134
+ )
135
+
136
+ result = await resp.json()
137
+
138
+ except (TimeoutError, aiohttp.ClientError) as err:
139
+ # Only the type name: aiohttp messages can contain the URL, and
140
+ # with it the API key.
141
+ raise HolfuyConnectionError(
142
+ f"Cannot reach Holfuy API: {type(err).__name__}"
143
+ ) from err
144
+ except ValueError as err:
145
+ raise HolfuyError("Unable to parse JSON response from Holfuy API") from err
146
+
147
+ # Request for a single station is not enveloped in a list of
148
+ # measurements objects. Make data consistent for all request types before it
149
+ # is returned.
150
+ if stations and len(stations) == 1:
151
+ result = {"measurements": [result]}
152
+
153
+ measurements = result.get("measurements") if isinstance(result, dict) else None
154
+ if not isinstance(measurements, list):
155
+ raise HolfuyError("Unexpected response from Holfuy API: no measurements")
156
+
157
+ if measurements and all(
158
+ m.get("errorCode") == "no_access" for m in measurements
159
+ ):
160
+ raise HolfuyAuthError("API key has no access to the requested stations")
161
+
162
+ return result
@@ -1,6 +1,6 @@
1
1
  CONF_MODE_JSON = "JSON" # We only handle JSON from the api
2
- CONF_MODE_XML = "CSV"
3
- CONF_MODE_CSV = "XML"
2
+ CONF_MODE_XML = "XML"
3
+ CONF_MODE_CSV = "CSV"
4
4
 
5
5
  CONF_AVERAGING_NEWEST = 0
6
6
  CONF_AVERAGING_QUARTERLY = 1
@@ -11,8 +11,8 @@ CONF_WIND_SPEED_UNIT_KMH = "km/h"
11
11
  CONF_WIND_SPEED_UNIT_MS = "m/s"
12
12
  CONF_WIND_SPEED_UNIT_MPH = "mph"
13
13
 
14
- CONF_TEMPERATURE_UNIT_CELSUIS = "Celsius"
15
- CONF_TEMPERATURE_UNIT_FAHRENHEIT = "Fahrenheit"
14
+ CONF_TEMPERATURE_UNIT_CELSIUS = "C"
15
+ CONF_TEMPERATURE_UNIT_FAHRENHEIT = "F"
16
16
 
17
17
  CONF_OPTION_BATT = "batt"
18
18
  CONF_OPTION_DAILY = "daily"
@@ -0,0 +1,13 @@
1
+ """Exceptions raised by the Holfuy library."""
2
+
3
+
4
+ class HolfuyError(Exception):
5
+ """Base class for all Holfuy errors, raised as is for unexpected replies."""
6
+
7
+
8
+ class HolfuyConnectionError(HolfuyError):
9
+ """The API could not be reached, timed out or returned an HTTP error."""
10
+
11
+
12
+ class HolfuyAuthError(HolfuyError):
13
+ """The API key was rejected, or it has no access to any requested station."""
@@ -0,0 +1,178 @@
1
+ import asyncio
2
+ from unittest import IsolatedAsyncioTestCase, mock
3
+ from unittest.mock import AsyncMock, MagicMock, patch
4
+ import json
5
+
6
+ import aiohttp
7
+
8
+ RESP_142 = json.loads("""{"stationId":142,"stationName":"THPK Ersfjord","location":{"latitude":"69.70017",
9
+ "longitude":"18.63842","altitude":130},"dateTime":"2025-05-19 15:45:00","wind":{"speed":9.4,"gust":13.3,"min":0,
10
+ "unit":"m/s","direction":245},"battery":4.15,"daily":{"max_temp":0,"min_temp":0},"temperature":0}""")
11
+
12
+ RESP_ALL = json.loads("""{"measurements":[{"stationId":142,"stationName":"THPK Ersfjord","location":{"latitude":"69.70017",
13
+ "longitude":"18.63842","altitude":130},"dateTime":"2025-05-19 15:45:00","wind":{"speed":9.4,"gust":13.3,"min":0,
14
+ "unit":"m/s","direction":245},"battery":4.15,"daily":{"max_temp":0,"min_temp":0},"temperature":0},
15
+ {"stationId":207,"stationName":"THPK Finnvikdalen","location":{"latitude":"69.76117",
16
+ "longitude":"18.85657","altitude":270},"dateTime":"2025-05-19 15:45:00",
17
+ "wind":{"speed":10,"gust":13.6,"min":0,"unit":"m/s","direction":339},"battery":4.14,
18
+ "daily":{"max_temp":7.5,"min_temp":4.5},"temperature":4.7},{"stationId":299,"stationName":"THPK Fjellheisen",
19
+ "location":{"latitude":"69.63116","longitude":"18.99377","altitude":420},"dateTime":"2025-05-19 15:45:00",
20
+ "wind":{"speed":7.8,"gust":10.3,"min":0,"unit":"m/s","direction":256},"battery":4.14,
21
+ "daily":{"max_temp":5.7,"min_temp":0},"temperature":3.6}]}""")
22
+
23
+ RESP_AUTHERROR=json.loads("""
24
+ {"measurements":[{"stationId":105,"error":"Sorry, you don't have access for this station.","errorCode":"no_access"},
25
+ {"stationId":106,"error":"Sorry, you don't have access for this station.","errorCode":"no_access"}]}""")
26
+
27
+ from holfuy import (
28
+ HolfuyAuthError,
29
+ HolfuyConnectionError,
30
+ HolfuyService,
31
+ )
32
+
33
+
34
+ def _get_returning(resp):
35
+ """Mock session.get() as an async context manager yielding resp."""
36
+ ctx = MagicMock()
37
+ ctx.__aenter__ = AsyncMock(return_value=resp)
38
+ ctx.__aexit__ = AsyncMock(return_value=False)
39
+ return MagicMock(return_value=ctx)
40
+
41
+ from holfuy.const import (
42
+ CONF_TEMPERATURE_UNIT_CELSIUS,
43
+ CONF_WIND_SPEED_UNIT_MS,
44
+ CONF_AVERAGING_NEWEST,
45
+ )
46
+
47
+
48
+ class TestHolfuyService(IsolatedAsyncioTestCase):
49
+ def setUp(self):
50
+ """Set up test cases."""
51
+ self.session = AsyncMock(spec=aiohttp.ClientSession)
52
+ self.api = HolfuyService(
53
+ "test_api_key",
54
+ self.session,
55
+ CONF_TEMPERATURE_UNIT_CELSIUS,
56
+ CONF_WIND_SPEED_UNIT_MS,
57
+ False,
58
+ )
59
+
60
+ async def test_fetch_data_single_station(self):
61
+ """Test fetching data for a single station."""
62
+ mock_response = RESP_142
63
+
64
+ mock_resp = AsyncMock()
65
+ mock_resp.status = 200
66
+ mock_resp.content_type = "application/json"
67
+ mock_resp.json = AsyncMock(return_value=mock_response)
68
+
69
+ self.session.get = _get_returning(mock_resp)
70
+
71
+ result = await self.api.fetch_data(["142"])
72
+
73
+ expected_params = {
74
+ "pw": "test_api_key",
75
+ "m": "JSON",
76
+ "tu": CONF_TEMPERATURE_UNIT_CELSIUS,
77
+ "su": CONF_WIND_SPEED_UNIT_MS,
78
+ "avg": CONF_AVERAGING_NEWEST,
79
+ "s": "142",
80
+ }
81
+
82
+ self.session.get.assert_called_once_with(
83
+ "https://api.holfuy.com/live/",
84
+ params=expected_params
85
+ )
86
+
87
+ self.assertEqual(result["measurements"][0]["stationId"], 142)
88
+
89
+ async def test_fetch_data_all_stations(self):
90
+ """Test fetching data for multiple stations."""
91
+ mock_response = RESP_ALL
92
+
93
+ mock_resp = AsyncMock()
94
+ mock_resp.status = 200
95
+ mock_resp.content_type = "application/json"
96
+ mock_resp.json = AsyncMock(return_value=mock_response)
97
+ self.session.get = _get_returning(mock_resp)
98
+
99
+ result = await self.api.fetch_data(None)
100
+
101
+ expected_params = {
102
+ "pw": "test_api_key",
103
+ "m": "JSON",
104
+ "tu": CONF_TEMPERATURE_UNIT_CELSIUS,
105
+ "su": CONF_WIND_SPEED_UNIT_MS,
106
+ "avg": CONF_AVERAGING_NEWEST,
107
+ "s": "all",
108
+ }
109
+
110
+ self.session.get.assert_called_once_with(
111
+ "https://api.holfuy.com/live/",
112
+ params=expected_params
113
+ )
114
+
115
+ self.assertEqual(result, mock_response)
116
+
117
+ async def test_fetch_data_error_response(self):
118
+ """Test handling of error responses."""
119
+ mock_resp = AsyncMock()
120
+ mock_resp.status = 404
121
+
122
+ self.session.get = _get_returning(mock_resp)
123
+
124
+ with self.assertRaises(HolfuyConnectionError):
125
+ await self.api.fetch_data(None)
126
+
127
+ async def test_fetch_data_invalid_access_token(self):
128
+ """Test handling of error responses."""
129
+ mock_resp = AsyncMock()
130
+ mock_resp.status = 200
131
+ mock_resp.content_type = "application/json"
132
+ mock_response = RESP_AUTHERROR
133
+
134
+ mock_resp.json = AsyncMock(return_value=mock_response)
135
+
136
+ self.session.get = _get_returning(mock_resp)
137
+
138
+ with self.assertRaises(HolfuyAuthError):
139
+ await self.api.fetch_data(["105","106"])
140
+
141
+ expected_params = {
142
+ "pw": "test_api_key",
143
+ "m": "JSON",
144
+ "tu": CONF_TEMPERATURE_UNIT_CELSIUS,
145
+ "su": CONF_WIND_SPEED_UNIT_MS,
146
+ "avg": CONF_AVERAGING_NEWEST,
147
+ "s": "105,106",
148
+ }
149
+
150
+ self.session.get.assert_called_once_with(
151
+ "https://api.holfuy.com/live/",
152
+ params=expected_params
153
+ )
154
+
155
+ async def test_fetch_data_timeout(self):
156
+ """Test handling of timeout errors."""
157
+ self.session.get = MagicMock(side_effect=asyncio.TimeoutError)
158
+
159
+ with self.assertRaises(HolfuyConnectionError):
160
+ await self.api.fetch_data(None)
161
+
162
+ async def test_fetch_data_client_error(self):
163
+ """Test handling of client errors."""
164
+ self.session.get = MagicMock(side_effect=aiohttp.ClientError)
165
+
166
+ with self.assertRaises(HolfuyConnectionError):
167
+ await self.api.fetch_data(None)
168
+
169
+ async def test_fetch_data_rejected_key(self):
170
+ """A rejected key gets HTTP 200 with a plain-text "No access" body."""
171
+ mock_resp = AsyncMock()
172
+ mock_resp.status = 200
173
+ mock_resp.content_type = "text/html"
174
+ mock_resp.text = AsyncMock(return_value="No access")
175
+ self.session.get = _get_returning(mock_resp)
176
+
177
+ with self.assertRaises(HolfuyAuthError):
178
+ await self.api.fetch_data(None)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyholfuy
3
- Version: 0.1
3
+ Version: 0.3
4
4
  Summary: A Python library for interacting with Holfuy weather stations.
5
5
  Author-email: Håvard Dagenborg <haavardj@dagenborg.net>
6
6
  License-Expression: MIT
@@ -16,15 +16,7 @@ Provides-Extra: test
16
16
  Requires-Dist: aioresponses==0.7.8; extra == "test"
17
17
  Requires-Dist: coverage==7.8.0; extra == "test"
18
18
  Requires-Dist: mypy==1.15.0; extra == "test"
19
- Requires-Dist: pytest-asyncio==0.26.0; extra == "test"
20
- Requires-Dist: pytest-cov==6.1.1; extra == "test"
21
- Requires-Dist: pytest-error-for-skips==2.0.2; extra == "test"
22
- Requires-Dist: pytest-timeout==2.3.1; extra == "test"
23
- Requires-Dist: pytest==8.3.5; extra == "test"
24
- Requires-Dist: ruff==0.11.7; extra == "test"
25
- Requires-Dist: syrupy==4.9.1; extra == "test"
26
19
  Provides-Extra: dev
27
- Requires-Dist: pre-commit==4.2.0; extra == "dev"
28
20
  Dynamic: license-file
29
21
 
30
22
  ![Holfy Logo](https://holfuy.com/image/logo/holfuy-logo.png)
@@ -34,7 +26,7 @@ A Python library for talking to [Holfuy Weather Stations](https://holfuy.com/).
34
26
 
35
27
  You need an API key to access the service. To obtain one, please visit the [Holfuy API](https://api.holfuy.com/live/) pages.
36
28
 
37
- The Library only access live data.
29
+ The library can only access live data.
38
30
 
39
31
  ## Example Code
40
32
  Here is an example of how to use the library.
@@ -46,10 +38,22 @@ from holfuy import HolfuyService
46
38
 
47
39
  async def main():
48
40
  async with aiohttp.ClientSession() as session:
49
- s = HolfuyService("", session)
50
- data = await s.fetch_data(["101"])
51
- print(data)
41
+ s = HolfuyService("", session)
42
+ data = await s.fetch_data(["101"])
43
+ print(data)
52
44
 
53
45
  if __name__ == "__main__":
54
46
  asyncio.run(main())
55
47
  ```
48
+
49
+ ## Return value and errors
50
+
51
+ `fetch_data()` returns `{"measurements": [...]}` with one entry per station, also when
52
+ a single station is requested. A station the key cannot read is still listed, with
53
+ `error` and `errorCode` instead of data.
54
+
55
+ It raises, all subclasses of `holfuy.HolfuyError`:
56
+
57
+ - `HolfuyAuthError`: the API key is rejected, or it has no access to any requested station.
58
+ - `HolfuyConnectionError`: network error, timeout, or an HTTP error status.
59
+ - `HolfuyError`: any other unexpected reply.
@@ -4,6 +4,8 @@ README.md
4
4
  pyproject.toml
5
5
  holfuy/__init__.py
6
6
  holfuy/const.py
7
+ holfuy/exceptions.py
8
+ holfuy/test_holfuy.py
7
9
  pyholfuy.egg-info/PKG-INFO
8
10
  pyholfuy.egg-info/SOURCES.txt
9
11
  pyholfuy.egg-info/dependency_links.txt
@@ -0,0 +1,8 @@
1
+ aiohttp>=3.9.4
2
+
3
+ [dev]
4
+
5
+ [test]
6
+ aioresponses==0.7.8
7
+ coverage==7.8.0
8
+ mypy==1.15.0
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "pyholfuy"
9
- version = "0.1"
9
+ version = "0.3"
10
10
  description = "A Python library for interacting with Holfuy weather stations."
11
11
  authors = [{name="Håvard Dagenborg", email="haavardj@dagenborg.net"}]
12
12
  license = "MIT"
@@ -29,24 +29,11 @@ test = [
29
29
  "aioresponses==0.7.8",
30
30
  "coverage==7.8.0",
31
31
  "mypy==1.15.0",
32
- "pytest-asyncio==0.26.0",
33
- "pytest-cov==6.1.1",
34
- "pytest-error-for-skips==2.0.2",
35
- "pytest-timeout==2.3.1",
36
- "pytest==8.3.5",
37
- "ruff==0.11.7",
38
- "syrupy==4.9.1",
39
32
  ]
40
33
  dev = [
41
- "pre-commit==4.2.0",
42
34
  ]
43
35
 
44
- [tool.ruff]
45
- line-length = 88
46
- indent-width = 4
47
- select = ["E", "F", "W", "C", "N", "Q"]
48
36
 
49
37
  [tool.pylint]
50
38
  max-line-length = 88
51
39
  disable = ["C0114", "C0115", "C0116"]
52
-
pyholfuy-0.1/README.md DELETED
@@ -1,26 +0,0 @@
1
- ![Holfy Logo](https://holfuy.com/image/logo/holfuy-logo.png)
2
-
3
- # PyHolfuy
4
- A Python library for talking to [Holfuy Weather Stations](https://holfuy.com/).
5
-
6
- You need an API key to access the service. To obtain one, please visit the [Holfuy API](https://api.holfuy.com/live/) pages.
7
-
8
- The Library only access live data.
9
-
10
- ## Example Code
11
- Here is an example of how to use the library.
12
-
13
- ```python
14
- import aiohttp
15
- import asyncio
16
- from holfuy import HolfuyService
17
-
18
- async def main():
19
- async with aiohttp.ClientSession() as session:
20
- s = HolfuyService("", session)
21
- data = await s.fetch_data(["101"])
22
- print(data)
23
-
24
- if __name__ == "__main__":
25
- asyncio.run(main())
26
- ```
@@ -1,131 +0,0 @@
1
- """Library to handle connection with Holfuy Live API."""
2
-
3
- import asyncio
4
- import logging
5
-
6
- from typing import Any
7
-
8
- import aiohttp
9
-
10
- from .const import (
11
- CONF_TEMPERATURE_UNIT_CELSUIS,
12
- CONF_WIND_SPEED_UNIT_MS,
13
- CONF_AVERAGING_NEWEST,
14
- CONF_MODE_JSON,
15
- STATIONS_ALL,
16
- )
17
-
18
- TIMEOUT = 30
19
- _LOGGER = logging.getLogger(__name__)
20
-
21
- API_URL = "http://api.holfuy.com/live/"
22
-
23
- # noqa: E501
24
- #### API Description
25
- # URL Format: http://api.holfuy.com/live/?s=101&pw=pass&m=JSON&tu=C&su=m/s
26
- #
27
- # s: station(s) ID e.g. s=101 or you can get the data from more stations by one query as well (coma separated list) e.g. s=101,102,103 . If you would get all stations' data (to which you have access) by one single API call please use the "all" string -> s=all .
28
- # pw: password for the API access (it could be sent by a POST "pw" parameter also)
29
- # m:mode XML or CSV or JSON default: CSV
30
- # avg:averaging "0": newest data from the station, "1" newest quarter hourly average, "2" newest hourly average (default = 0)
31
- # su: wind speed unit: knots, km/h, m/s, mph, default: m/s
32
- # tu: temperature unit C:Celsius , F: Fahrenheit default: Celsius
33
- # batt: add this parameter to get the station's battery voltage (JSON only).
34
- # cam: add this parameter to the URL for timestamp of the last picture from the camera. (JSON only, only for stations with a camera)
35
- # daily: add this parameter to get daily Max-Min temperatures and precipitation since last midnight CE(S)T (JSON only, max 5 stations)
36
- # loc: add this parameter to the URL to get the station's location in reply too (JSON only)
37
- # utc: add this parameter to the URL for timestamps in UTC, otherwise times will be in CE(S)T
38
- # Data: DateTime: time of the last data / average in ".date('T')." (Central European Time) or in UTC if utc param is set.
39
- #####
40
-
41
-
42
- class HolfuyService:
43
- """Representation of Holfuy weather data."""
44
-
45
- def __init__(
46
- self,
47
- api_key: str,
48
- websession=None,
49
- temperature_unit=CONF_TEMPERATURE_UNIT_CELSUIS,
50
- wind_speed_unit=CONF_WIND_SPEED_UNIT_MS,
51
- timestamps_in_utc=False,
52
- ) -> None:
53
- """Initialize the Weather object."""
54
-
55
- self._api_url = API_URL
56
- self._api_key = api_key
57
- self._wind_speed_unit = wind_speed_unit
58
- self._temperature_unit = temperature_unit
59
- self._timestamps_in_utc = timestamps_in_utc
60
-
61
- if websession is None:
62
-
63
- async def _create_session():
64
- return aiohttp.ClientSession()
65
-
66
- loop = asyncio.get_event_loop()
67
- self._websession = loop.run_until_complete(_create_session())
68
- else:
69
- self._websession = websession
70
-
71
- async def fetch_data(
72
- self,
73
- stations: list[str] | None = None,
74
- averaging_mode=CONF_AVERAGING_NEWEST,
75
- include_battery=False,
76
- include_daily=False,
77
- include_location=False,
78
- ) -> dict[str, Any] | None:
79
- """Get the latest data from Holfuy."""
80
-
81
- params = {
82
- "pw": self._api_key,
83
- "m": CONF_MODE_JSON,
84
- "tu": self._temperature_unit,
85
- "su": self._wind_speed_unit,
86
- "avg": averaging_mode,
87
- }
88
-
89
- if self._timestamps_in_utc:
90
- params["utc"] = ""
91
-
92
- if include_battery:
93
- params["batt"] = ""
94
-
95
- if include_daily:
96
- params["daily"] = ""
97
-
98
- if include_location:
99
- params["loc"] = ""
100
-
101
- if stations:
102
- params["s"] = ",".join(map(str, stations))
103
- else:
104
- params["s"] = STATIONS_ALL
105
-
106
- try:
107
- async with asyncio.timeout(TIMEOUT):
108
- resp = await self._websession.get(self._api_url, params=params)
109
-
110
- if resp.status >= 400:
111
- _LOGGER.error("%s returned %s", self._api_url, resp.status)
112
- return None
113
-
114
- result = await resp.json()
115
-
116
- # Request for a single station is not enveloped in a list of
117
- # measurements objects. Make data consistent for all request types before it
118
- # is returned.
119
- if stations and len(stations) == 1:
120
- result = {"measurements": result}
121
-
122
- return result
123
-
124
- except (TimeoutError, aiohttp.ClientError) as err:
125
- _LOGGER.error(
126
- "Access to %s returned error '%s'", self._api_url, type(err).__name__
127
- )
128
- return None
129
- except ValueError:
130
- _LOGGER.exception("Unable to parse json response from %s", self._api_url)
131
- return None
@@ -1,16 +0,0 @@
1
- aiohttp>=3.9.4
2
-
3
- [dev]
4
- pre-commit==4.2.0
5
-
6
- [test]
7
- aioresponses==0.7.8
8
- coverage==7.8.0
9
- mypy==1.15.0
10
- pytest-asyncio==0.26.0
11
- pytest-cov==6.1.1
12
- pytest-error-for-skips==2.0.2
13
- pytest-timeout==2.3.1
14
- pytest==8.3.5
15
- ruff==0.11.7
16
- syrupy==4.9.1
File without changes
File without changes
File without changes