open-data-bz-meteo 1.0.0__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.
@@ -0,0 +1,20 @@
1
+ This work is licensed under Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0).
2
+
3
+ You are free to:
4
+
5
+ - Share - copy and redistribute the material in any medium or format.
6
+ - Adapt - remix, transform, and build upon the material.
7
+
8
+ The licensor cannot revoke these freedoms as long as you follow the license terms.
9
+
10
+ Under the following terms:
11
+
12
+ - Attribution - You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
13
+
14
+ - ShareAlike - If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.
15
+
16
+ No additional restrictions - You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
17
+
18
+ Disclaimer - The licensor makes no warranties about the material, and is not liable for any damages arising from its use.
19
+
20
+ To view a copy of this license, visit https://creativecommons.org/licenses/by-sa/4.0/.
@@ -0,0 +1,69 @@
1
+ Metadata-Version: 2.4
2
+ Name: open_data_bz_meteo
3
+ Version: 1.0.0
4
+ Summary: Python API client for the open meteo data V1 service, provided by Provincia autonoma di Bolzano - Informatica Alto Adige SPA.
5
+ Author-email: david vnl <io@davole.com>
6
+ License-Expression: CC-BY-SA-4.0
7
+ Project-URL: Homepage, https://github.com/dvdvnl/open_data_bz_meteo
8
+ Project-URL: Issues, https://github.com/dvdvnl/open_data_bz_meteo/issues
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Requires-Python: >=3.12.0
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE.md
13
+ Requires-Dist: requests>=2.28
14
+ Requires-Dist: pydantic>=2.0
15
+ Provides-Extra: develop
16
+ Requires-Dist: build>=1.4.0; extra == "develop"
17
+ Requires-Dist: pytest-cov>=7.0; extra == "develop"
18
+ Requires-Dist: pytest>=7.0; extra == "develop"
19
+ Requires-Dist: requests-mock>=1.9.3; extra == "develop"
20
+ Requires-Dist: twine>=6.2.0; extra == "develop"
21
+ Dynamic: license-file
22
+
23
+ # Open Data BZ Meteo - API Client
24
+
25
+ Python API client for the "Open Meteo Data V1" service, provided by _Provincia autonoma di Bolzano - Informatica Alto Adige SPA_. The API documentation can be found [here](https://data.civis.bz.it/dataset/misure-meteo-e-idrografiche).
26
+
27
+ ## Core classes
28
+
29
+ ### `Client`
30
+
31
+ Main entrypoint to the API.
32
+
33
+ - `get_stations()` fetches all stations and caches them on the client.
34
+ - `get_station(station_code)` looks up a single station by code (refreshes cache).
35
+ - `get_sensors(station)` fetches sensors for a given station.
36
+ - `get_sensor(station, sensor_type)` fetches a single sensor and raises `ValueError` if not found.
37
+
38
+ ### `Station`
39
+
40
+ - Represents a station with location data (`latitude`, `longitude`, `altitude`) and localized names (`name_deu`, `name_eng`, `name_ita`, `name_lld`).
41
+ - `sensor_types`: sensor types available at this station.
42
+ - `get_sensor(sensor_type)`: returns the first matching sensor or `None`.
43
+
44
+ ### `Sensor`
45
+
46
+ - Represents a single sensor reading with `type`, `unit`, `value`, and `date`.
47
+ - `parsed_datetime`: computed `datetime` parsed from the raw `date` string.
48
+ - Localized description (`description_deu`, `description_ita`, `description_lld`).
49
+
50
+ ## Usage
51
+
52
+ ```python
53
+ from open_data_bz_meteo import Client
54
+
55
+ client = Client(timeout=5)
56
+
57
+ # List available stations
58
+ stations = client.get_stations()
59
+ print(stations[0].name_eng, stations[0].latitude)
60
+
61
+ # Fetch sensors for a single station
62
+ station = stations[0]
63
+ station.sensors = client.get_sensors(station)
64
+ print([s.type for s in station.sensors])
65
+
66
+ # Fetch a single sensor by type
67
+ sensor = client.get_sensor(station, "WT")
68
+ print(sensor.value, sensor.unit)
69
+ ```
@@ -0,0 +1,47 @@
1
+ # Open Data BZ Meteo - API Client
2
+
3
+ Python API client for the "Open Meteo Data V1" service, provided by _Provincia autonoma di Bolzano - Informatica Alto Adige SPA_. The API documentation can be found [here](https://data.civis.bz.it/dataset/misure-meteo-e-idrografiche).
4
+
5
+ ## Core classes
6
+
7
+ ### `Client`
8
+
9
+ Main entrypoint to the API.
10
+
11
+ - `get_stations()` fetches all stations and caches them on the client.
12
+ - `get_station(station_code)` looks up a single station by code (refreshes cache).
13
+ - `get_sensors(station)` fetches sensors for a given station.
14
+ - `get_sensor(station, sensor_type)` fetches a single sensor and raises `ValueError` if not found.
15
+
16
+ ### `Station`
17
+
18
+ - Represents a station with location data (`latitude`, `longitude`, `altitude`) and localized names (`name_deu`, `name_eng`, `name_ita`, `name_lld`).
19
+ - `sensor_types`: sensor types available at this station.
20
+ - `get_sensor(sensor_type)`: returns the first matching sensor or `None`.
21
+
22
+ ### `Sensor`
23
+
24
+ - Represents a single sensor reading with `type`, `unit`, `value`, and `date`.
25
+ - `parsed_datetime`: computed `datetime` parsed from the raw `date` string.
26
+ - Localized description (`description_deu`, `description_ita`, `description_lld`).
27
+
28
+ ## Usage
29
+
30
+ ```python
31
+ from open_data_bz_meteo import Client
32
+
33
+ client = Client(timeout=5)
34
+
35
+ # List available stations
36
+ stations = client.get_stations()
37
+ print(stations[0].name_eng, stations[0].latitude)
38
+
39
+ # Fetch sensors for a single station
40
+ station = stations[0]
41
+ station.sensors = client.get_sensors(station)
42
+ print([s.type for s in station.sensors])
43
+
44
+ # Fetch a single sensor by type
45
+ sensor = client.get_sensor(station, "WT")
46
+ print(sensor.value, sensor.unit)
47
+ ```
@@ -0,0 +1,7 @@
1
+ """Open Data BZ Meteo Client"""
2
+
3
+ from .client import Client
4
+ from .sensor import Sensor
5
+ from .station import Station
6
+
7
+ __all__ = ["Client", "Sensor", "Station"]
@@ -0,0 +1,137 @@
1
+ from __future__ import annotations
2
+
3
+ import requests
4
+
5
+ from open_data_bz_meteo.const import API_URL
6
+
7
+ from .sensor import Sensor
8
+ from .station import Station
9
+
10
+
11
+ class Client:
12
+ def __init__(
13
+ self,
14
+ base_url: str = API_URL,
15
+ timeout: int = 10,
16
+ session: requests.Session | None = None,
17
+ ) -> None:
18
+ """Create a client.
19
+
20
+ Args:
21
+ base_url: Base URL of the Open Meteo Data service (no trailing slash).
22
+ timeout: Default request timeout in seconds.
23
+ session: Optional requests.Session for advanced usage / testing.
24
+ """
25
+ self.base_url = base_url.rstrip("/")
26
+ self.session = session or requests.Session()
27
+ self.timeout = timeout
28
+
29
+ self.stations: list[Station] = []
30
+ self.sensors: list[Sensor] = []
31
+
32
+ # Get all stations
33
+ def get_stations(self) -> list[Station]:
34
+ """Fetch all stations from the API and cache them in `self.stations`.
35
+
36
+ Returns:
37
+ list[Station]: All stations returned by the service.
38
+ """
39
+
40
+ # GET request to the API
41
+ response = self.session.get(
42
+ f"{self.base_url}/stations",
43
+ timeout=self.timeout,
44
+ )
45
+ response.raise_for_status()
46
+
47
+ data = response.json().get("features", [])
48
+
49
+ # Rebuild station list to avoid duplicates
50
+ self.stations = []
51
+ for feature in data:
52
+ station_props = feature.get("properties", {})
53
+ self.stations.append(Station.model_validate(station_props))
54
+
55
+ return self.stations
56
+
57
+ # Get a single station
58
+ def get_station(self, station_code: str) -> Station | None:
59
+ """Return the station matching `station_code`, refreshing the station list.
60
+
61
+ Args:
62
+ station_code: The station code to look up.
63
+
64
+ Returns:
65
+ Optional[Station]: The matched station, or None if not found.
66
+ """
67
+
68
+ # Get all stations
69
+ self.get_stations()
70
+
71
+ # Loop through stations and return the station with the given station_code
72
+ for station in self.stations:
73
+ if station.station_code == station_code:
74
+ return station
75
+
76
+ # Get sensors from station
77
+ def get_sensors(self, station: Station) -> list[Sensor]:
78
+ """Fetch sensors for a specific station.
79
+
80
+ Args:
81
+ station: The station to fetch sensors for.
82
+
83
+ Returns:
84
+ list[Sensor]: Sensors belonging to the station.
85
+ """
86
+
87
+ sensors: list[Sensor] = []
88
+
89
+ # GET request to the API
90
+ response = self.session.get(
91
+ f"{self.base_url}/sensors",
92
+ params={"station_code": station.station_code},
93
+ timeout=self.timeout,
94
+ )
95
+
96
+ response.raise_for_status()
97
+
98
+ # Get the data from the response
99
+ data = response.json() or []
100
+
101
+ # Loop through data and create sensor objects via pydantic
102
+ for sensor_data in data:
103
+ sensors.append(Sensor.model_validate(sensor_data))
104
+
105
+ return sensors
106
+
107
+ # Get a single sensor from a station
108
+ def get_sensor(self, station: Station, sensor_type: str) -> Sensor:
109
+ """Fetch a single sensor by type for a station.
110
+
111
+ Args:
112
+ station: The station to query.
113
+ sensor_type: The sensor type code to retrieve.
114
+
115
+ Raises:
116
+ ValueError: If no sensor with the given type exists for the station.
117
+
118
+ Returns:
119
+ Sensor: The requested sensor.
120
+ """
121
+
122
+ # GET request to the API
123
+ response = self.session.get(
124
+ f"{self.base_url}/sensors",
125
+ params={"station_code": station.station_code, "sensor_code": sensor_type},
126
+ timeout=self.timeout,
127
+ )
128
+
129
+ response.raise_for_status()
130
+
131
+ data = response.json() or []
132
+ if not data:
133
+ raise ValueError(
134
+ f"No sensor found with type '{sensor_type}' for station '{station.station_code}'"
135
+ )
136
+
137
+ return Sensor.model_validate(data[0])
@@ -0,0 +1,2 @@
1
+ DATE_FORMAT = "%Y-%m-%dT%H:%M:%S%Z"
2
+ API_URL = "https://geoservices.buergernetz.bz.it/services/meteo/v1"
@@ -0,0 +1,29 @@
1
+ import datetime
2
+
3
+ from pydantic import BaseModel, Field, computed_field
4
+
5
+ from .const import DATE_FORMAT
6
+
7
+
8
+ class Sensor(BaseModel):
9
+ """Represents a single sensor reading from the Open Meteo Data API.
10
+
11
+ This model is created from API response records and provides a
12
+ computed ``parsed_datetime`` property for easy datetime access.
13
+ """
14
+
15
+ date: str = Field(..., alias="DATE")
16
+ description_deu: str = Field(..., alias="DESC_D")
17
+ description_ita: str = Field(..., alias="DESC_I")
18
+ description_lld: str = Field(..., alias="DESC_L")
19
+ station_code: str = Field(..., alias="SCODE")
20
+ type: str = Field(..., alias="TYPE")
21
+ unit: str = Field(..., alias="UNIT")
22
+ value: str | float = Field(..., alias="VALUE")
23
+
24
+ @computed_field
25
+ def parsed_datetime(self) -> "datetime.datetime":
26
+ """Parsed :class:`datetime.datetime` from the raw ``date`` string."""
27
+ return datetime.datetime.strptime(self.date, DATE_FORMAT).replace(
28
+ tzinfo=datetime.UTC
29
+ )
@@ -0,0 +1,32 @@
1
+ from pydantic import BaseModel, Field, computed_field
2
+
3
+ from .sensor import Sensor
4
+
5
+
6
+ class Station(BaseModel):
7
+ """Represents a meteorological station in the Open Meteo Data API.
8
+
9
+ This model includes location data, localized station names, and an optional
10
+ list of sensors that can be populated via the API client.
11
+ """
12
+
13
+ altitude: float = Field(..., alias="ALT")
14
+ latitude: float = Field(..., alias="LAT")
15
+ longitude: float = Field(..., alias="LONG")
16
+ name_deu: str | None = Field(..., alias="NAME_D")
17
+ name_eng: str | None = Field(..., alias="NAME_E")
18
+ name_ita: str | None = Field(..., alias="NAME_I")
19
+ name_lld: str | None = Field(..., alias="NAME_L")
20
+ sensors: list[Sensor] = Field(default_factory=list)
21
+ station_code: str = Field(..., alias="SCODE")
22
+
23
+ @computed_field
24
+ def sensor_types(self) -> list[str]:
25
+ """Return a list of sensor types available at this station."""
26
+ return [sensor.type for sensor in self.sensors] if self.sensors else []
27
+
28
+ def get_sensor(self, sensor_type: str) -> Sensor | None:
29
+ """Return the first sensor matching `sensor_type`, or `None` if not found."""
30
+ for sensor in self.sensors:
31
+ if sensor.type == sensor_type:
32
+ return sensor
@@ -0,0 +1,69 @@
1
+ Metadata-Version: 2.4
2
+ Name: open_data_bz_meteo
3
+ Version: 1.0.0
4
+ Summary: Python API client for the open meteo data V1 service, provided by Provincia autonoma di Bolzano - Informatica Alto Adige SPA.
5
+ Author-email: david vnl <io@davole.com>
6
+ License-Expression: CC-BY-SA-4.0
7
+ Project-URL: Homepage, https://github.com/dvdvnl/open_data_bz_meteo
8
+ Project-URL: Issues, https://github.com/dvdvnl/open_data_bz_meteo/issues
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Requires-Python: >=3.12.0
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE.md
13
+ Requires-Dist: requests>=2.28
14
+ Requires-Dist: pydantic>=2.0
15
+ Provides-Extra: develop
16
+ Requires-Dist: build>=1.4.0; extra == "develop"
17
+ Requires-Dist: pytest-cov>=7.0; extra == "develop"
18
+ Requires-Dist: pytest>=7.0; extra == "develop"
19
+ Requires-Dist: requests-mock>=1.9.3; extra == "develop"
20
+ Requires-Dist: twine>=6.2.0; extra == "develop"
21
+ Dynamic: license-file
22
+
23
+ # Open Data BZ Meteo - API Client
24
+
25
+ Python API client for the "Open Meteo Data V1" service, provided by _Provincia autonoma di Bolzano - Informatica Alto Adige SPA_. The API documentation can be found [here](https://data.civis.bz.it/dataset/misure-meteo-e-idrografiche).
26
+
27
+ ## Core classes
28
+
29
+ ### `Client`
30
+
31
+ Main entrypoint to the API.
32
+
33
+ - `get_stations()` fetches all stations and caches them on the client.
34
+ - `get_station(station_code)` looks up a single station by code (refreshes cache).
35
+ - `get_sensors(station)` fetches sensors for a given station.
36
+ - `get_sensor(station, sensor_type)` fetches a single sensor and raises `ValueError` if not found.
37
+
38
+ ### `Station`
39
+
40
+ - Represents a station with location data (`latitude`, `longitude`, `altitude`) and localized names (`name_deu`, `name_eng`, `name_ita`, `name_lld`).
41
+ - `sensor_types`: sensor types available at this station.
42
+ - `get_sensor(sensor_type)`: returns the first matching sensor or `None`.
43
+
44
+ ### `Sensor`
45
+
46
+ - Represents a single sensor reading with `type`, `unit`, `value`, and `date`.
47
+ - `parsed_datetime`: computed `datetime` parsed from the raw `date` string.
48
+ - Localized description (`description_deu`, `description_ita`, `description_lld`).
49
+
50
+ ## Usage
51
+
52
+ ```python
53
+ from open_data_bz_meteo import Client
54
+
55
+ client = Client(timeout=5)
56
+
57
+ # List available stations
58
+ stations = client.get_stations()
59
+ print(stations[0].name_eng, stations[0].latitude)
60
+
61
+ # Fetch sensors for a single station
62
+ station = stations[0]
63
+ station.sensors = client.get_sensors(station)
64
+ print([s.type for s in station.sensors])
65
+
66
+ # Fetch a single sensor by type
67
+ sensor = client.get_sensor(station, "WT")
68
+ print(sensor.value, sensor.unit)
69
+ ```
@@ -0,0 +1,13 @@
1
+ LICENSE.md
2
+ README.md
3
+ pyproject.toml
4
+ open_data_bz_meteo/__init__.py
5
+ open_data_bz_meteo/client.py
6
+ open_data_bz_meteo/const.py
7
+ open_data_bz_meteo/sensor.py
8
+ open_data_bz_meteo/station.py
9
+ open_data_bz_meteo.egg-info/PKG-INFO
10
+ open_data_bz_meteo.egg-info/SOURCES.txt
11
+ open_data_bz_meteo.egg-info/dependency_links.txt
12
+ open_data_bz_meteo.egg-info/requires.txt
13
+ open_data_bz_meteo.egg-info/top_level.txt
@@ -0,0 +1,9 @@
1
+ requests>=2.28
2
+ pydantic>=2.0
3
+
4
+ [develop]
5
+ build>=1.4.0
6
+ pytest-cov>=7.0
7
+ pytest>=7.0
8
+ requests-mock>=1.9.3
9
+ twine>=6.2.0
@@ -0,0 +1 @@
1
+ open_data_bz_meteo
@@ -0,0 +1,40 @@
1
+ [project]
2
+ name = "open_data_bz_meteo"
3
+ version = "1.0.0"
4
+ authors = [
5
+ { name="david vnl", email="io@davole.com" },
6
+ ]
7
+ description = "Python API client for the open meteo data V1 service, provided by Provincia autonoma di Bolzano - Informatica Alto Adige SPA."
8
+ readme = "README.md"
9
+ license = "CC-BY-SA-4.0"
10
+ requires-python = ">=3.12.0"
11
+ dependencies = [
12
+ "requests>=2.28",
13
+ "pydantic>=2.0",
14
+ ]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3.12"
17
+ ]
18
+
19
+ [build-system]
20
+ requires = ["setuptools>=65", "wheel"]
21
+ build-backend = "setuptools.build_meta"
22
+
23
+ # Optional groups for development and testing
24
+ [project.optional-dependencies]
25
+ develop = [
26
+ "build>=1.4.0",
27
+ "pytest-cov>=7.0",
28
+ "pytest>=7.0",
29
+ "requests-mock>=1.9.3",
30
+ "twine>=6.2.0",
31
+ ]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/dvdvnl/open_data_bz_meteo"
35
+ Issues = "https://github.com/dvdvnl/open_data_bz_meteo/issues"
36
+
37
+ [tool.pytest.ini_options]
38
+ addopts = "--cov=open_data_bz_meteo --cov-config=.coveragerc -q"
39
+ python_files = "tests/**/*.py"
40
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+