python-meteolux 0.1.0__py3-none-any.whl
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.
- meteolux/__init__.py +5 -0
- meteolux/async_api.py +252 -0
- meteolux/exceptions.py +59 -0
- meteolux/models.py +331 -0
- python_meteolux-0.1.0.dist-info/METADATA +30 -0
- python_meteolux-0.1.0.dist-info/RECORD +7 -0
- python_meteolux-0.1.0.dist-info/WHEEL +4 -0
meteolux/__init__.py
ADDED
meteolux/async_api.py
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""A Python client for the MeteoLux API."""
|
|
2
|
+
import typing
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from .exceptions import NotFoundError
|
|
8
|
+
from .models import (
|
|
9
|
+
ATCReport,
|
|
10
|
+
Bookmarks,
|
|
11
|
+
InObservation,
|
|
12
|
+
ObservationMetadataResponse,
|
|
13
|
+
ObservationResponse,
|
|
14
|
+
User,
|
|
15
|
+
WeatherResponse,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AsyncMeteoLuxClient:
|
|
20
|
+
"""A Python client for the MeteoLux API, built with httpx and Pydantic.
|
|
21
|
+
|
|
22
|
+
This client is generated from the OpenAPI specification and provides
|
|
23
|
+
methods for all available endpoints, returning structured Pydantic models.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, base_url: str = 'https://metapi.ana.lu/api/v1') -> None:
|
|
27
|
+
"""Initializes the client with the base URL.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
base_url (str): The base URL for the API.
|
|
31
|
+
"""
|
|
32
|
+
self.base_url = base_url
|
|
33
|
+
self.client = httpx.AsyncClient(base_url=self.base_url, timeout=10.0)
|
|
34
|
+
|
|
35
|
+
async def _request(self, method: str, endpoint: str, response_model: Optional[Any] = None, **kwargs: Any) -> Any:
|
|
36
|
+
"""Internal method to handle all API requests and common error handling.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
method (str): The HTTP method (e.g., "GET", "POST").
|
|
40
|
+
endpoint (str): The API endpoint path.
|
|
41
|
+
response_model (Optional[Any]): The Pydantic model to use for response parsing.
|
|
42
|
+
**kwargs: Additional arguments for the httpx request (e.g., params, json).
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Any: The Pydantic model instance or raw JSON data.
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
NotFoundError: If the API returns a 404 Not Found status code.
|
|
49
|
+
httpx.HTTPStatusError: If the response status code is another error.
|
|
50
|
+
httpx.RequestError: For network-related issues.
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
response = await self.client.request(method, endpoint, **kwargs)
|
|
54
|
+
response.raise_for_status()
|
|
55
|
+
|
|
56
|
+
if response.status_code == 204:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
data = response.json()
|
|
60
|
+
if response_model:
|
|
61
|
+
return response_model.model_validate(data)
|
|
62
|
+
return data
|
|
63
|
+
|
|
64
|
+
except httpx.HTTPStatusError as exc:
|
|
65
|
+
if exc.response.status_code == 404:
|
|
66
|
+
raise NotFoundError(detail=exc.response.text) from exc
|
|
67
|
+
|
|
68
|
+
raise
|
|
69
|
+
except httpx.RequestError:
|
|
70
|
+
raise
|
|
71
|
+
|
|
72
|
+
# --- ATC Endpoints ---
|
|
73
|
+
|
|
74
|
+
async def get_atc_report(self) -> ATCReport:
|
|
75
|
+
"""Get data for the ATC dashboard.
|
|
76
|
+
|
|
77
|
+
Corresponds to GET /atc/report.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
ATCReport: An ATCReport Pydantic model instance.
|
|
81
|
+
"""
|
|
82
|
+
endpoint = '/atc/report'
|
|
83
|
+
return await self._request('GET', endpoint, response_model=ATCReport)
|
|
84
|
+
|
|
85
|
+
# --- HVD Endpoints ---
|
|
86
|
+
|
|
87
|
+
async def get_observations_hvd(self) -> ObservationResponse:
|
|
88
|
+
"""Return last minute observation data.
|
|
89
|
+
|
|
90
|
+
Corresponds to GET /hvd/observations.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
ObservationResponse: An ObservationResponse Pydantic model instance.
|
|
94
|
+
"""
|
|
95
|
+
endpoint = '/hvd/observations'
|
|
96
|
+
return await self._request('GET', endpoint, response_model=ObservationResponse)
|
|
97
|
+
|
|
98
|
+
async def get_observations_metadata_hvd(self) -> ObservationMetadataResponse:
|
|
99
|
+
"""Return observations metadata.
|
|
100
|
+
|
|
101
|
+
Corresponds to GET /hvd/observations/metadata.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
ObservationMetadataResponse: An ObservationMetadataResponse Pydantic model instance.
|
|
105
|
+
"""
|
|
106
|
+
endpoint = '/hvd/observations/metadata'
|
|
107
|
+
return await self._request('GET', endpoint, response_model=ObservationMetadataResponse)
|
|
108
|
+
|
|
109
|
+
async def get_station_information_hvd(self, station_id: str) -> list[Any]:
|
|
110
|
+
"""Return station information, by ID.
|
|
111
|
+
|
|
112
|
+
Corresponds to GET /hvd/stations/{station_id}.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
station_id (str): The ID of the station to retrieve.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Any: Station data from the response. The OpenAPI spec indicates the return type is a list of Stations.
|
|
119
|
+
"""
|
|
120
|
+
endpoint = f'/hvd/stations/{station_id}'
|
|
121
|
+
return await self._request('GET', endpoint)
|
|
122
|
+
|
|
123
|
+
async def get_all_station_information_hvd(self) -> list[Any]:
|
|
124
|
+
"""Return all station information.
|
|
125
|
+
|
|
126
|
+
Corresponds to GET /hvd/stations.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
list[Any]: A list of station objects. The OpenAPI spec indicates the return type is a list of Stations.
|
|
130
|
+
"""
|
|
131
|
+
endpoint = '/hvd/stations'
|
|
132
|
+
return await self._request('GET', endpoint)
|
|
133
|
+
|
|
134
|
+
# --- MetApp Endpoints ---
|
|
135
|
+
|
|
136
|
+
async def get_weather(self, langcode: str = 'fr', city: Optional[int] = None, lat: Optional[float] = None, long: Optional[float] = None) -> WeatherResponse:
|
|
137
|
+
"""Get weather for a city/language or lat/long.
|
|
138
|
+
|
|
139
|
+
Corresponds to GET /metapp/weather.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
langcode (str): The language code (fr, de, en, lb).
|
|
143
|
+
city (Optional[int]): The city ID.
|
|
144
|
+
lat (Optional[float]): Latitude.
|
|
145
|
+
long (Optional[float]): Longitude.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
WeatherResponse: A WeatherResponse Pydantic model instance.
|
|
149
|
+
"""
|
|
150
|
+
endpoint = '/metapp/weather'
|
|
151
|
+
params: dict[str, str] = {'langcode': langcode}
|
|
152
|
+
if city is not None:
|
|
153
|
+
params['city'] = str(city)
|
|
154
|
+
if lat is not None:
|
|
155
|
+
params['lat'] = str(lat)
|
|
156
|
+
if long is not None:
|
|
157
|
+
params['long'] = str(long)
|
|
158
|
+
return await self._request('GET', endpoint, params=params, response_model=WeatherResponse)
|
|
159
|
+
|
|
160
|
+
async def update_user(self, user_data: User) -> None:
|
|
161
|
+
"""Add or update a user's token and preferences.
|
|
162
|
+
|
|
163
|
+
Corresponds to POST /metapp/user.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
user_data (User): A Pydantic User model instance.
|
|
167
|
+
"""
|
|
168
|
+
endpoint = '/metapp/user'
|
|
169
|
+
await self._request('POST', endpoint, json=user_data.model_dump(by_alias=True))
|
|
170
|
+
|
|
171
|
+
async def get_bookmarks(self, langcode: str = 'fr', lat: Optional[float] = None, long: Optional[float] = None) -> Bookmarks:
|
|
172
|
+
"""Return all cities and the closest one if lat/long are given.
|
|
173
|
+
|
|
174
|
+
Corresponds to GET /metapp/bookmarks.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
langcode (str): The language code (fr, de, en, lb).
|
|
178
|
+
lat (Optional[float]): Latitude.
|
|
179
|
+
long (Optional[float]): Longitude.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Bookmarks: A Bookmarks Pydantic model instance.
|
|
183
|
+
"""
|
|
184
|
+
endpoint = '/metapp/bookmarks'
|
|
185
|
+
params: dict[str, str] = {'langcode': langcode}
|
|
186
|
+
if lat is not None:
|
|
187
|
+
params['lat'] = str(lat)
|
|
188
|
+
if long is not None:
|
|
189
|
+
params['long'] = str(long)
|
|
190
|
+
return await self._request('GET', endpoint, params=params, response_model=Bookmarks)
|
|
191
|
+
|
|
192
|
+
async def get_interface_texts(self, lang: typing.Literal['fr', 'de', 'en', 'lb'] = 'fr') -> dict[str, Any]:
|
|
193
|
+
"""Return translated interface texts for the mobile app.
|
|
194
|
+
|
|
195
|
+
Note: The spec for this endpoint's response is an un-typed object.
|
|
196
|
+
|
|
197
|
+
Corresponds to GET /metapp/text.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
lang (str): The language code (fr, de, en, lb).
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
dict[str, Any]: A dictionary with translated strings.
|
|
204
|
+
"""
|
|
205
|
+
endpoint = '/metapp/text'
|
|
206
|
+
params = {'lang': lang}
|
|
207
|
+
return await self._request('GET', endpoint, params=params)
|
|
208
|
+
|
|
209
|
+
async def stream_image(self, filename: str) -> httpx.Response:
|
|
210
|
+
"""Stream an image from the cluster.
|
|
211
|
+
|
|
212
|
+
Corresponds to GET /metapp/image/{filename}.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
filename (str): The name of the image file.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
httpx.Response: The raw httpx Response object to handle streaming.
|
|
219
|
+
"""
|
|
220
|
+
endpoint = f'/metapp/image/{filename}'
|
|
221
|
+
return await self.client.get(endpoint, timeout=10.0)
|
|
222
|
+
|
|
223
|
+
async def get_observations_metapp(self) -> list[Any]:
|
|
224
|
+
"""Return participative observations in the last 30 minutes.
|
|
225
|
+
|
|
226
|
+
Note: The spec for this endpoint's response is an array of untyped objects.
|
|
227
|
+
|
|
228
|
+
Corresponds to GET /metapp/observations.
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
list[Any]: A list of objects.
|
|
232
|
+
"""
|
|
233
|
+
endpoint = '/metapp/observations'
|
|
234
|
+
return await self._request('GET', endpoint)
|
|
235
|
+
|
|
236
|
+
async def add_observation(self, observation_data: InObservation) -> str:
|
|
237
|
+
"""Add a new observation.
|
|
238
|
+
|
|
239
|
+
Corresponds to POST /metapp/observation.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
observation_data (InObservation): An InObservation Pydantic model instance.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
str: The successful response message.
|
|
246
|
+
"""
|
|
247
|
+
endpoint = '/metapp/observation'
|
|
248
|
+
return await self._request('POST', endpoint, json=observation_data.model_dump())
|
|
249
|
+
|
|
250
|
+
async def close(self) -> None:
|
|
251
|
+
"""Closes the httpx client."""
|
|
252
|
+
await self.client.aclose()
|
meteolux/exceptions.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Custom exceptions for the MeteoLux API client."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MeteoLuxError(Exception):
|
|
5
|
+
"""Base exception for the MeteoLux API client."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class NotFoundError(MeteoLuxError):
|
|
9
|
+
"""Raised when the API returns a 404 Not Found status."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, detail: str) -> None:
|
|
12
|
+
"""Initializes the NotFoundError with a detail message.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
detail (str): The error message from the API.
|
|
16
|
+
"""
|
|
17
|
+
self.detail = detail
|
|
18
|
+
super().__init__(self.detail)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ValidationError(MeteoLuxError):
|
|
22
|
+
"""Raised when there is a validation error in the Pydantic models."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, detail: str) -> None:
|
|
25
|
+
"""Initializes the ValidationError with a detail message.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
detail (str): The error message from the API.
|
|
29
|
+
"""
|
|
30
|
+
self.detail = detail
|
|
31
|
+
super().__init__(self.detail)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class HTTPValidationError(MeteoLuxError):
|
|
35
|
+
"""Raised when the API returns a 422 Unprocessable Entity status."""
|
|
36
|
+
|
|
37
|
+
def __init__(self, detail: str) -> None:
|
|
38
|
+
"""Initializes the HTTPValidationError with a detail message.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
detail (str): The error message from the API.
|
|
42
|
+
"""
|
|
43
|
+
self.detail = detail
|
|
44
|
+
super().__init__(self.detail)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class HTTPError(MeteoLuxError):
|
|
48
|
+
"""A generic HTTP error."""
|
|
49
|
+
|
|
50
|
+
def __init__(self, status_code: int, detail: str) -> None:
|
|
51
|
+
"""Initializes the HTTPError with a status code and detail message.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
status_code (int): The HTTP status code.
|
|
55
|
+
detail (str): The error message from the API.
|
|
56
|
+
"""
|
|
57
|
+
self.status_code = status_code
|
|
58
|
+
self.detail = detail
|
|
59
|
+
super().__init__(f'HTTP Error {self.status_code}: {self.detail}')
|
meteolux/models.py
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""API models."""
|
|
2
|
+
|
|
3
|
+
from datetime import date, datetime
|
|
4
|
+
from typing import Literal, Optional, Union
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Icon(BaseModel):
|
|
10
|
+
"""Base icon."""
|
|
11
|
+
|
|
12
|
+
id: int
|
|
13
|
+
name: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Wind(BaseModel):
|
|
17
|
+
"""Wind model."""
|
|
18
|
+
|
|
19
|
+
direction: str
|
|
20
|
+
speed: str
|
|
21
|
+
gusts: Optional[str] = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Temperature(BaseModel):
|
|
25
|
+
"""Temperature model."""
|
|
26
|
+
|
|
27
|
+
temperature: Union[int, list[int]]
|
|
28
|
+
humidex: Optional[str] = None
|
|
29
|
+
felt: Optional[int] = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class CurrentWeather(BaseModel):
|
|
33
|
+
"""Part of the global weather model."""
|
|
34
|
+
|
|
35
|
+
date: datetime
|
|
36
|
+
icon: Icon
|
|
37
|
+
wind: Wind
|
|
38
|
+
rain: str
|
|
39
|
+
snow: str
|
|
40
|
+
type: Literal['current'] = 'current'
|
|
41
|
+
temperature: Temperature
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class DailyWeather(BaseModel):
|
|
45
|
+
"""For the list of following days."""
|
|
46
|
+
|
|
47
|
+
date: datetime
|
|
48
|
+
icon: Icon
|
|
49
|
+
wind: Wind
|
|
50
|
+
rain: str
|
|
51
|
+
snow: str
|
|
52
|
+
type: Literal['daily'] = 'daily'
|
|
53
|
+
temperature_min: Temperature = Field(..., alias='temperatureMin')
|
|
54
|
+
temperature_max: Temperature = Field(..., alias='temperatureMax')
|
|
55
|
+
sunshine: int
|
|
56
|
+
uv_index: int = Field(..., alias='uvIndex')
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class HourlyWeather(BaseModel):
|
|
60
|
+
"""For the list of following hours."""
|
|
61
|
+
|
|
62
|
+
date: datetime
|
|
63
|
+
icon: Icon
|
|
64
|
+
wind: Wind
|
|
65
|
+
rain: str
|
|
66
|
+
snow: str
|
|
67
|
+
type: Literal['hourly'] = 'hourly'
|
|
68
|
+
temperature: Temperature
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Trend(BaseModel):
|
|
72
|
+
"""Forecast part of data."""
|
|
73
|
+
|
|
74
|
+
date: date
|
|
75
|
+
min_temp: float = Field(..., alias='minTemp')
|
|
76
|
+
max_temp: float = Field(..., alias='maxTemp')
|
|
77
|
+
precipitation: float
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class Climatology(BaseModel):
|
|
81
|
+
"""History part of data."""
|
|
82
|
+
|
|
83
|
+
date: datetime
|
|
84
|
+
min_temp: float = Field(..., alias='minTemp')
|
|
85
|
+
max_temp: float = Field(..., alias='maxTemp')
|
|
86
|
+
precipitation: float
|
|
87
|
+
mean_temp: float = Field(..., alias='meanTemp')
|
|
88
|
+
sunshine: Optional[float] = None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class GraphicalData(BaseModel):
|
|
92
|
+
"""Graphical group of data."""
|
|
93
|
+
|
|
94
|
+
history: list[Climatology]
|
|
95
|
+
forecast: list[Trend]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class Vigilance(BaseModel):
|
|
99
|
+
"""Vigilance model."""
|
|
100
|
+
|
|
101
|
+
datetime_start: datetime = Field(..., alias='datetimeStart')
|
|
102
|
+
datetime_end: datetime = Field(..., alias='datetimeEnd')
|
|
103
|
+
level: Literal[2, 3, 4]
|
|
104
|
+
type: int
|
|
105
|
+
group: int
|
|
106
|
+
region: Literal['north', 'south', 'all']
|
|
107
|
+
description: str
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class RoadStatusItem(BaseModel):
|
|
111
|
+
"""Road status item model as per the spec."""
|
|
112
|
+
|
|
113
|
+
date: Union[date, list[str]]
|
|
114
|
+
description: str
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class ImageOut(BaseModel):
|
|
118
|
+
"""Image with url."""
|
|
119
|
+
|
|
120
|
+
date: datetime
|
|
121
|
+
provider: str
|
|
122
|
+
url: str = Field(..., max_length=2083, min_length=1)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class Radar(BaseModel):
|
|
126
|
+
"""Radar image model."""
|
|
127
|
+
|
|
128
|
+
real_time: list[ImageOut] = Field(..., alias='realTime')
|
|
129
|
+
forecast: list[ImageOut]
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class Satellite(BaseModel):
|
|
133
|
+
"""Satellite image model."""
|
|
134
|
+
|
|
135
|
+
infrared: list[ImageOut]
|
|
136
|
+
visual: list[ImageOut]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class MoonIcon(BaseModel):
|
|
140
|
+
"""As different id are used."""
|
|
141
|
+
|
|
142
|
+
id: str
|
|
143
|
+
name: str
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class Ephemeris(BaseModel):
|
|
147
|
+
"""Ephemeris model."""
|
|
148
|
+
|
|
149
|
+
date: date
|
|
150
|
+
sunrise: str
|
|
151
|
+
sunset: str
|
|
152
|
+
moonrise: str
|
|
153
|
+
moonset: str
|
|
154
|
+
sunshine: str
|
|
155
|
+
moon_icon: MoonIcon = Field(..., alias='moonIcon')
|
|
156
|
+
uv_index: int = Field(..., alias='uvIndex', ge=0.0, le=12.0)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class ATCReportForecast(BaseModel):
|
|
160
|
+
"""Forecast for ATC dashboard."""
|
|
161
|
+
|
|
162
|
+
hourly: list['HourlyWindForecast']
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class ATCReport(BaseModel):
|
|
166
|
+
"""Data for ATC dashboard."""
|
|
167
|
+
|
|
168
|
+
forecast: ATCReportForecast
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class HourlyWindForecast(BaseModel):
|
|
172
|
+
"""Hourly wind report, at different altitude (feet)."""
|
|
173
|
+
|
|
174
|
+
date: datetime
|
|
175
|
+
qnh: int
|
|
176
|
+
wind: Wind
|
|
177
|
+
wind1500: Wind
|
|
178
|
+
wind2500: Wind
|
|
179
|
+
wind5000: Wind
|
|
180
|
+
wind10000: Wind
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class BookmarkCity(BaseModel):
|
|
184
|
+
"""With additional info for mobile app ep."""
|
|
185
|
+
|
|
186
|
+
id: int
|
|
187
|
+
name: str
|
|
188
|
+
region: Literal['N', 'S'] = 'S'
|
|
189
|
+
canton: Literal[
|
|
190
|
+
'Capellen', 'Clervaux', 'Diekirch', 'Echternach', 'Esch-sur-Alzette', 'Grevenmacher', 'Luxembourg', 'Mersch', 'Redange', 'Remich', 'Vianden', 'Wiltz'
|
|
191
|
+
]
|
|
192
|
+
domain: Literal['villes', 'lieu', 'fluvial']
|
|
193
|
+
lat: float
|
|
194
|
+
long: float
|
|
195
|
+
temperature: float
|
|
196
|
+
icon: Icon
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class Bookmarks(BaseModel):
|
|
200
|
+
"""Bookmarks model."""
|
|
201
|
+
|
|
202
|
+
cities: list[BookmarkCity]
|
|
203
|
+
nearest_city: Optional[BookmarkCity] = Field(None, alias='nearestCity')
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class InObservation(BaseModel):
|
|
207
|
+
"""Observation from public users."""
|
|
208
|
+
|
|
209
|
+
lat: float = Field(..., ge=-90.0, le=90.0)
|
|
210
|
+
long: float = Field(..., ge=-180.0, le=180.0)
|
|
211
|
+
description: str = Field(..., max_length=1024)
|
|
212
|
+
weather: int
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class SensorLevel(BaseModel):
|
|
216
|
+
"""Sensor level definition."""
|
|
217
|
+
|
|
218
|
+
level_type: Literal['height_above_ground'] = Field(..., alias='levelType')
|
|
219
|
+
unit: Literal['m']
|
|
220
|
+
value: float = Field(..., ge=0.0)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class ObservationMetadata(BaseModel):
|
|
224
|
+
"""Sensor definition."""
|
|
225
|
+
|
|
226
|
+
id: str
|
|
227
|
+
name: str
|
|
228
|
+
description: str
|
|
229
|
+
data_type: Literal['realtime', 'climate'] = Field(..., alias='dataType')
|
|
230
|
+
unit: Literal['m', 'm/s', '%', '1/10 kt', 'degC', 'degrees', 'ft', 'hPa', 'mm']
|
|
231
|
+
category: Literal['Wind', 'Cloud Cover', 'Atmospheric pressure', 'Precipitation', 'Temperature', 'Humidity', 'Visibility']
|
|
232
|
+
performance_category: Literal['A', 'B', 'C', 'D', 'E'] = Field(..., alias='performanceCategory')
|
|
233
|
+
qualitycode: Literal[0, 1, 2, 3, 4, 5, 6, 7]
|
|
234
|
+
timeoffsets: Literal['PT0H']
|
|
235
|
+
timeresolution: Literal['PT1M', 'PT1H']
|
|
236
|
+
sensorlevels: Optional[SensorLevel] = None
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class ObservationMetadataResponse(BaseModel):
|
|
240
|
+
"""Elements metadata."""
|
|
241
|
+
|
|
242
|
+
licence: list[str] = ['Creative Commons', 'https://creativecommons.org/public-domain/cc0/']
|
|
243
|
+
doc_url: str = Field('/docs', alias='docUrl')
|
|
244
|
+
data: list[ObservationMetadata]
|
|
245
|
+
total_item_count: int = Field(1, alias='totalItemCount')
|
|
246
|
+
quality_codes: dict[str, str] = Field({'0': 'Value is controlled and found O.K.'}, alias='qualityCodes')
|
|
247
|
+
performance_category: dict[str, str] = Field(
|
|
248
|
+
{'A': 'The sensor type fulfills the requirements from WMO/CIMOs on measurement accuracy, calibration and maintenance.'}, alias='performanceCategory'
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class ObservationResponseData(BaseModel):
|
|
253
|
+
"""Model to link gendata id to their values."""
|
|
254
|
+
|
|
255
|
+
id: str
|
|
256
|
+
value: Union[int, float]
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class ObservationResponse(BaseModel):
|
|
260
|
+
"""Last Observations."""
|
|
261
|
+
|
|
262
|
+
licence: list[str] = ['Creative Commons', 'https://creativecommons.org/public-domain/cc0/']
|
|
263
|
+
doc_url: str = Field('/docs', alias='docUrl')
|
|
264
|
+
data: list[ObservationResponseData]
|
|
265
|
+
total_item_count: int = Field(1, alias='totalItemCount')
|
|
266
|
+
timestamp: datetime
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class OutCity(BaseModel):
|
|
270
|
+
"""City with translated name."""
|
|
271
|
+
|
|
272
|
+
id: int
|
|
273
|
+
name: str
|
|
274
|
+
region: Literal['N', 'S'] = 'S'
|
|
275
|
+
canton: Literal[
|
|
276
|
+
'Capellen', 'Clervaux', 'Diekirch', 'Echternach', 'Esch-sur-Alzette', 'Grevenmacher', 'Luxembourg', 'Mersch', 'Redange', 'Remich', 'Vianden', 'Wiltz'
|
|
277
|
+
]
|
|
278
|
+
domain: Literal['villes', 'lieu', 'fluvial']
|
|
279
|
+
lat: float
|
|
280
|
+
long: float
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class VigilanceSettings(BaseModel):
|
|
284
|
+
"""User settings for notifications."""
|
|
285
|
+
|
|
286
|
+
level: Literal[2, 3, 4]
|
|
287
|
+
type_air: bool = Field(False, alias='typeAir')
|
|
288
|
+
type_cold: bool = Field(False, alias='typeCold')
|
|
289
|
+
type_flooding: bool = Field(False, alias='typeFlooding')
|
|
290
|
+
type_heat: bool = Field(False, alias='typeHeat')
|
|
291
|
+
type_ice: bool = Field(False, alias='typeIce')
|
|
292
|
+
type_rain: bool = Field(False, alias='typeRain')
|
|
293
|
+
type_snow: bool = Field(False, alias='typeSnow')
|
|
294
|
+
type_storm: bool = Field(False, alias='typeStorm')
|
|
295
|
+
type_wind: Optional[bool] = Field(None, alias='typeWind')
|
|
296
|
+
zone_north: bool = Field(..., alias='zoneNorth')
|
|
297
|
+
zone_south: bool = Field(..., alias='zoneSouth')
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class User(BaseModel):
|
|
301
|
+
"""User model."""
|
|
302
|
+
|
|
303
|
+
language: Literal['fr', 'de', 'en', 'lb']
|
|
304
|
+
push_token: str = Field(..., alias='pushToken', max_length=50)
|
|
305
|
+
push_morning: bool = Field(False, alias='pushMorning')
|
|
306
|
+
push_evening: bool = Field(False, alias='pushEvening')
|
|
307
|
+
device: str
|
|
308
|
+
version: str
|
|
309
|
+
buildversion: str
|
|
310
|
+
vigilance: VigilanceSettings
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
class WeatherResponseForecast(BaseModel):
|
|
314
|
+
"""Forecast model."""
|
|
315
|
+
|
|
316
|
+
current: CurrentWeather
|
|
317
|
+
hourly: list[HourlyWeather]
|
|
318
|
+
daily: list[DailyWeather]
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
class WeatherResponse(BaseModel):
|
|
322
|
+
"""Final weather output from the backend."""
|
|
323
|
+
|
|
324
|
+
city: OutCity
|
|
325
|
+
forecast: WeatherResponseForecast
|
|
326
|
+
vigilances: list[Vigilance]
|
|
327
|
+
road_status: list[RoadStatusItem] = Field(..., alias='roadStatus')
|
|
328
|
+
ephemeris: Ephemeris
|
|
329
|
+
radar: Radar
|
|
330
|
+
satellite: Satellite
|
|
331
|
+
data: GraphicalData
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-meteolux
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: python library for accessing the MeteoLux REST API
|
|
5
|
+
Project-URL: Homepage, https://github.com/sim0nx/python-meteolux
|
|
6
|
+
Project-URL: Download, https://github.com/sim0nx/python-meteolux
|
|
7
|
+
Project-URL: Tracker, https://github.com/sim0nx/python-meteolux/issues
|
|
8
|
+
Project-URL: Documentation, http://python-meteolux.readthedocs.io/en/latest/?badge=latest
|
|
9
|
+
Project-URL: Source, https://github.com/sim0nx/python-meteolux
|
|
10
|
+
Author-email: Georges Toth <georges@trypill.org>
|
|
11
|
+
License: AGPLv3+
|
|
12
|
+
Keywords: MeteoLux
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Requires-Dist: httpx
|
|
25
|
+
Requires-Dist: pydantic
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
An early implementation of the MeteoLux REST API.
|
|
29
|
+
|
|
30
|
+
https://metapi.ana.lu/api/v1/docs
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
meteolux/__init__.py,sha256=SOvcvxmHp7DoSe68cohBEmGHrh7FyYLJ8or4HlCL03k,105
|
|
2
|
+
meteolux/async_api.py,sha256=0qs9bWBFOKbTSw2FAPiclWtEIKc7CPXV1QV1WTzJh40,8034
|
|
3
|
+
meteolux/exceptions.py,sha256=eGiv_oDBHfEDQ0vHNGnekELt43seRusYMk3vBqfSBgg,1630
|
|
4
|
+
meteolux/models.py,sha256=-J7mirv69yTm2RMl7tlH0qnLXs3qWTaTCKE1-kE893Q,8062
|
|
5
|
+
python_meteolux-0.1.0.dist-info/METADATA,sha256=alcXHlWcjNsL07sPX3v4xBlVaucVUqmeorw7VWOKdj0,1291
|
|
6
|
+
python_meteolux-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
7
|
+
python_meteolux-0.1.0.dist-info/RECORD,,
|