meteo-lt-pkg 0.2.4__py3-none-any.whl → 0.4.0b1__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.
- meteo_lt/__init__.py +9 -2
- meteo_lt/api.py +74 -27
- meteo_lt/client.py +80 -0
- meteo_lt/const.py +108 -0
- meteo_lt/models.py +28 -35
- meteo_lt/warnings.py +183 -0
- meteo_lt_pkg-0.4.0b1.dist-info/METADATA +324 -0
- meteo_lt_pkg-0.4.0b1.dist-info/RECORD +12 -0
- {meteo_lt_pkg-0.2.4.dist-info → meteo_lt_pkg-0.4.0b1.dist-info}/WHEEL +1 -1
- meteo_lt_pkg-0.2.4.dist-info/METADATA +0 -178
- meteo_lt_pkg-0.2.4.dist-info/RECORD +0 -9
- {meteo_lt_pkg-0.2.4.dist-info → meteo_lt_pkg-0.4.0b1.dist-info/licenses}/LICENSE +0 -0
- {meteo_lt_pkg-0.2.4.dist-info → meteo_lt_pkg-0.4.0b1.dist-info}/top_level.txt +0 -0
meteo_lt/__init__.py
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
"""init.py"""
|
|
2
2
|
|
|
3
3
|
from .api import MeteoLtAPI
|
|
4
|
-
from .models import Coordinates, Place, ForecastTimestamp, Forecast
|
|
4
|
+
from .models import Coordinates, Place, ForecastTimestamp, Forecast, WeatherWarning
|
|
5
5
|
|
|
6
|
-
__all__ = [
|
|
6
|
+
__all__ = [
|
|
7
|
+
"MeteoLtAPI",
|
|
8
|
+
"Coordinates",
|
|
9
|
+
"Place",
|
|
10
|
+
"ForecastTimestamp",
|
|
11
|
+
"Forecast",
|
|
12
|
+
"WeatherWarning",
|
|
13
|
+
]
|
meteo_lt/api.py
CHANGED
|
@@ -1,31 +1,39 @@
|
|
|
1
1
|
"""Main API class script"""
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# pylint: disable=W0718
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
from .models import Forecast, WeatherWarning
|
|
6
8
|
from .utils import find_nearest_place
|
|
9
|
+
from .client import MeteoLtClient
|
|
10
|
+
from .warnings import WeatherWarningsProcessor
|
|
7
11
|
|
|
8
12
|
|
|
9
13
|
class MeteoLtAPI:
|
|
10
|
-
"""Main API class"""
|
|
11
|
-
|
|
12
|
-
BASE_URL = "https://api.meteo.lt/v1"
|
|
13
|
-
TIMEOUT = 30
|
|
14
|
-
ENCODING = "utf-8"
|
|
14
|
+
"""Main API class that orchestrates external API calls and warning processing"""
|
|
15
15
|
|
|
16
|
-
def __init__(self):
|
|
16
|
+
def __init__(self, session=None):
|
|
17
17
|
self.places = []
|
|
18
|
+
self.client = MeteoLtClient(session)
|
|
19
|
+
self.warnings_processor = WeatherWarningsProcessor(self.client)
|
|
20
|
+
|
|
21
|
+
async def __aenter__(self):
|
|
22
|
+
"""Async context manager entry"""
|
|
23
|
+
await self.client.__aenter__()
|
|
24
|
+
return self
|
|
25
|
+
|
|
26
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
27
|
+
"""Async context manager exit"""
|
|
28
|
+
await self.client.__aexit__(exc_type, exc_val, exc_tb)
|
|
29
|
+
|
|
30
|
+
async def close(self):
|
|
31
|
+
"""Close the API client and cleanup resources"""
|
|
32
|
+
await self.client.close()
|
|
18
33
|
|
|
19
34
|
async def fetch_places(self):
|
|
20
35
|
"""Gets all places from API"""
|
|
21
|
-
|
|
22
|
-
timeout=aiohttp.ClientTimeout(total=self.TIMEOUT)
|
|
23
|
-
) as session:
|
|
24
|
-
async with session.get(f"{self.BASE_URL}/places") as response:
|
|
25
|
-
response.raise_for_status()
|
|
26
|
-
response.encoding = self.ENCODING
|
|
27
|
-
response_json = await response.json()
|
|
28
|
-
self.places = [Place.from_dict(place) for place in response_json]
|
|
36
|
+
self.places = await self.client.fetch_places()
|
|
29
37
|
|
|
30
38
|
async def get_nearest_place(self, latitude, longitude):
|
|
31
39
|
"""Finds nearest place using provided coordinates"""
|
|
@@ -33,15 +41,54 @@ class MeteoLtAPI:
|
|
|
33
41
|
await self.fetch_places()
|
|
34
42
|
return find_nearest_place(latitude, longitude, self.places)
|
|
35
43
|
|
|
36
|
-
async def
|
|
44
|
+
async def get_forecast_with_warnings(
|
|
45
|
+
self, latitude=None, longitude=None, place_code=None
|
|
46
|
+
):
|
|
47
|
+
"""Get forecast with weather warnings for a location"""
|
|
48
|
+
if place_code is None:
|
|
49
|
+
if latitude is None or longitude is None:
|
|
50
|
+
raise ValueError(
|
|
51
|
+
"Either place_code or both latitude and longitude must be provided"
|
|
52
|
+
)
|
|
53
|
+
place = await self.get_nearest_place(latitude, longitude)
|
|
54
|
+
place_code = place.code
|
|
55
|
+
|
|
56
|
+
return await self.get_forecast(place_code, include_warnings=True)
|
|
57
|
+
|
|
58
|
+
async def get_forecast(self, place_code, include_warnings=True):
|
|
37
59
|
"""Retrieves forecast data from API"""
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
60
|
+
forecast = await self.client.fetch_forecast(place_code)
|
|
61
|
+
|
|
62
|
+
if include_warnings:
|
|
63
|
+
await self._enrich_forecast_with_warnings(forecast)
|
|
64
|
+
|
|
65
|
+
return forecast
|
|
66
|
+
|
|
67
|
+
async def get_weather_warnings(
|
|
68
|
+
self, administrative_division: str = None
|
|
69
|
+
) -> List[WeatherWarning]:
|
|
70
|
+
"""Fetches weather warnings from meteo.lt JSON API"""
|
|
71
|
+
return await self.warnings_processor.get_weather_warnings(
|
|
72
|
+
administrative_division
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
async def _enrich_forecast_with_warnings(self, forecast: Forecast):
|
|
76
|
+
"""Enrich forecast timestamps with relevant weather warnings"""
|
|
77
|
+
if (
|
|
78
|
+
not forecast
|
|
79
|
+
or not forecast.place
|
|
80
|
+
or not forecast.place.administrative_division
|
|
81
|
+
):
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
warnings = await self.get_weather_warnings(
|
|
86
|
+
forecast.place.administrative_division
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if warnings:
|
|
90
|
+
self.warnings_processor.enrich_forecast_with_warnings(
|
|
91
|
+
forecast, warnings
|
|
92
|
+
)
|
|
93
|
+
except Exception as e:
|
|
94
|
+
print(f"Warning: Could not fetch weather warnings: {e}")
|
meteo_lt/client.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""MeteoLt API client for external API calls"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
|
|
6
|
+
import aiohttp
|
|
7
|
+
|
|
8
|
+
from .models import Place, Forecast
|
|
9
|
+
from .const import BASE_URL, WARNINGS_URL, TIMEOUT, ENCODING
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MeteoLtClient:
|
|
13
|
+
"""Client for external API calls to meteo.lt"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, session: Optional[aiohttp.ClientSession] = None):
|
|
16
|
+
self._session = session
|
|
17
|
+
self._owns_session = session is None
|
|
18
|
+
|
|
19
|
+
async def __aenter__(self):
|
|
20
|
+
"""Async context manager entry"""
|
|
21
|
+
if self._session is None:
|
|
22
|
+
self._session = aiohttp.ClientSession(
|
|
23
|
+
timeout=aiohttp.ClientTimeout(total=TIMEOUT),
|
|
24
|
+
raise_for_status=True,
|
|
25
|
+
)
|
|
26
|
+
return self
|
|
27
|
+
|
|
28
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
29
|
+
"""Async context manager exit"""
|
|
30
|
+
await self.close()
|
|
31
|
+
|
|
32
|
+
async def close(self):
|
|
33
|
+
"""Close the client session if we own it"""
|
|
34
|
+
if self._session and self._owns_session:
|
|
35
|
+
await self._session.close()
|
|
36
|
+
self._session = None
|
|
37
|
+
|
|
38
|
+
async def _get_session(self) -> aiohttp.ClientSession:
|
|
39
|
+
"""Get or create a session"""
|
|
40
|
+
if self._session is None:
|
|
41
|
+
self._session = aiohttp.ClientSession(
|
|
42
|
+
timeout=aiohttp.ClientTimeout(total=TIMEOUT),
|
|
43
|
+
raise_for_status=True,
|
|
44
|
+
)
|
|
45
|
+
return self._session
|
|
46
|
+
|
|
47
|
+
async def fetch_places(self) -> List[Place]:
|
|
48
|
+
"""Gets all places from API"""
|
|
49
|
+
session = await self._get_session()
|
|
50
|
+
async with session.get(f"{BASE_URL}/places") as response:
|
|
51
|
+
response.encoding = ENCODING
|
|
52
|
+
response_json = await response.json()
|
|
53
|
+
return [Place.from_dict(place) for place in response_json]
|
|
54
|
+
|
|
55
|
+
async def fetch_forecast(self, place_code: str) -> Forecast:
|
|
56
|
+
"""Retrieves forecast data from API"""
|
|
57
|
+
session = await self._get_session()
|
|
58
|
+
async with session.get(
|
|
59
|
+
f"{BASE_URL}/places/{place_code}/forecasts/long-term"
|
|
60
|
+
) as response:
|
|
61
|
+
response.encoding = ENCODING
|
|
62
|
+
response_json = await response.json()
|
|
63
|
+
return Forecast.from_dict(response_json)
|
|
64
|
+
|
|
65
|
+
async def fetch_weather_warnings(self) -> List[dict]:
|
|
66
|
+
"""Fetches raw weather warnings data from meteo.lt JSON API"""
|
|
67
|
+
session = await self._get_session()
|
|
68
|
+
|
|
69
|
+
# Get the latest warnings file
|
|
70
|
+
async with session.get(WARNINGS_URL) as response:
|
|
71
|
+
file_list = await response.json()
|
|
72
|
+
|
|
73
|
+
if not file_list:
|
|
74
|
+
return []
|
|
75
|
+
|
|
76
|
+
# Fetch the latest warnings data
|
|
77
|
+
latest_file_url = file_list[0] # First file is the most recent
|
|
78
|
+
async with session.get(latest_file_url) as response:
|
|
79
|
+
text_data = await response.text()
|
|
80
|
+
return json.loads(text_data)
|
meteo_lt/const.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""const.py"""
|
|
2
|
+
|
|
3
|
+
BASE_URL = "https://api.meteo.lt/v1"
|
|
4
|
+
WARNINGS_URL = (
|
|
5
|
+
"https://www.meteo.lt/app/mu-plugins/Meteo/Components/"
|
|
6
|
+
"WeatherWarningsNew/list_JSON.php"
|
|
7
|
+
)
|
|
8
|
+
TIMEOUT = 30
|
|
9
|
+
ENCODING = "utf-8"
|
|
10
|
+
|
|
11
|
+
# Define the county to administrative divisions mapping
|
|
12
|
+
# https://www.infolex.lt/teise/DocumentSinglePart.aspx?AktoId=125125&StrNr=5#
|
|
13
|
+
COUNTY_MUNICIPALITIES = {
|
|
14
|
+
"Alytaus apskritis": [
|
|
15
|
+
"Alytaus miesto",
|
|
16
|
+
"Alytaus rajono",
|
|
17
|
+
"Druskininkų",
|
|
18
|
+
"Lazdijų rajono",
|
|
19
|
+
"Varėnos rajono",
|
|
20
|
+
],
|
|
21
|
+
"Kauno apskritis": [
|
|
22
|
+
"Birštono",
|
|
23
|
+
"Jonavos rajono",
|
|
24
|
+
"Kaišiadorių rajono",
|
|
25
|
+
"Kauno miesto",
|
|
26
|
+
"Kauno rajono",
|
|
27
|
+
"Kėdainių rajono",
|
|
28
|
+
"Prienų rajono",
|
|
29
|
+
"Raseinių rajono",
|
|
30
|
+
],
|
|
31
|
+
"Klaipėdos apskritis": [
|
|
32
|
+
"Klaipėdos rajono",
|
|
33
|
+
"Klaipėdos miesto",
|
|
34
|
+
"Kretingos rajono",
|
|
35
|
+
"Neringos",
|
|
36
|
+
"Palangos miesto",
|
|
37
|
+
"Skuodo rajono",
|
|
38
|
+
"Šilutės rajono",
|
|
39
|
+
],
|
|
40
|
+
"Marijampolės apskritis": [
|
|
41
|
+
"Kalvarijos",
|
|
42
|
+
"Kazlų Rūdos",
|
|
43
|
+
"Marijampolės",
|
|
44
|
+
"Šakių rajono",
|
|
45
|
+
"Vilkaviškio rajono",
|
|
46
|
+
],
|
|
47
|
+
"Panevėžio apskritis": [
|
|
48
|
+
"Biržų rajono",
|
|
49
|
+
"Kupiškio rajono",
|
|
50
|
+
"Panevėžio miesto",
|
|
51
|
+
"Panevėžio rajono",
|
|
52
|
+
"Pasvalio rajono",
|
|
53
|
+
"Rokiškio rajono",
|
|
54
|
+
],
|
|
55
|
+
"Šiaulių apskritis": [
|
|
56
|
+
"Joniškio rajono",
|
|
57
|
+
"Kelmės rajono",
|
|
58
|
+
"Pakruojo rajono",
|
|
59
|
+
"Akmenės rajono",
|
|
60
|
+
"Radviliškio rajono",
|
|
61
|
+
"Šiaulių miesto",
|
|
62
|
+
"Šiaulių rajono",
|
|
63
|
+
],
|
|
64
|
+
"Tauragės apskritis": [
|
|
65
|
+
"Jurbarko rajono",
|
|
66
|
+
"Pagėgių",
|
|
67
|
+
"Šilalės rajono",
|
|
68
|
+
"Tauragės rajono",
|
|
69
|
+
],
|
|
70
|
+
"Telšių apskritis": [
|
|
71
|
+
"Mažeikių rajono",
|
|
72
|
+
"Plungės rajono",
|
|
73
|
+
"Rietavo",
|
|
74
|
+
"Telšių rajono",
|
|
75
|
+
],
|
|
76
|
+
"Utenos apskritis": [
|
|
77
|
+
"Anykščių rajono",
|
|
78
|
+
"Ignalinos rajono",
|
|
79
|
+
"Molėtų rajono",
|
|
80
|
+
"Utenos rajono",
|
|
81
|
+
"Visagino",
|
|
82
|
+
"Zarasų rajono",
|
|
83
|
+
],
|
|
84
|
+
"Vilniaus apskritis": [
|
|
85
|
+
"Elektrėnų",
|
|
86
|
+
"Šalčininkų rajono",
|
|
87
|
+
"Širvintų rajono",
|
|
88
|
+
"Švenčionių rajono",
|
|
89
|
+
"Trakų rajono",
|
|
90
|
+
"Ukmergės rajono",
|
|
91
|
+
"Vilniaus miesto",
|
|
92
|
+
"Vilniaus rajono",
|
|
93
|
+
],
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# Artificial area for Baltic coast and Curonian Lagoon created by meteo.lt
|
|
97
|
+
# Adding separately for better visibility
|
|
98
|
+
COUNTY_MUNICIPALITIES.update(
|
|
99
|
+
{
|
|
100
|
+
"Pietryčių Baltija, Kuršių marios": [
|
|
101
|
+
"Klaipėdos rajono",
|
|
102
|
+
"Klaipėdos miesto",
|
|
103
|
+
"Neringos",
|
|
104
|
+
"Palangos miesto",
|
|
105
|
+
"Šilutės rajono",
|
|
106
|
+
],
|
|
107
|
+
}
|
|
108
|
+
)
|
meteo_lt/models.py
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass, field, fields
|
|
4
4
|
from datetime import datetime, timezone
|
|
5
|
-
from typing import List
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
|
|
7
|
+
from .const import COUNTY_MUNICIPALITIES
|
|
6
8
|
|
|
7
9
|
|
|
8
10
|
@dataclass
|
|
@@ -22,8 +24,9 @@ class Place:
|
|
|
22
24
|
administrative_division: str = field(
|
|
23
25
|
metadata={"json_key": "administrativeDivision"}
|
|
24
26
|
)
|
|
25
|
-
|
|
27
|
+
country_code: str = field(metadata={"json_key": "countryCode"})
|
|
26
28
|
coordinates: Coordinates
|
|
29
|
+
counties: List[str] = field(init=False)
|
|
27
30
|
|
|
28
31
|
@property
|
|
29
32
|
def latitude(self):
|
|
@@ -35,6 +38,15 @@ class Place:
|
|
|
35
38
|
"""Longitude from coordinates"""
|
|
36
39
|
return self.coordinates.longitude
|
|
37
40
|
|
|
41
|
+
def __post_init__(self):
|
|
42
|
+
self.counties = []
|
|
43
|
+
for county, municipalities in COUNTY_MUNICIPALITIES.items():
|
|
44
|
+
if (
|
|
45
|
+
self.administrative_division.replace(" savivaldybė", "")
|
|
46
|
+
in municipalities
|
|
47
|
+
):
|
|
48
|
+
self.counties.append(county)
|
|
49
|
+
|
|
38
50
|
|
|
39
51
|
@dataclass
|
|
40
52
|
class ForecastTimestamp:
|
|
@@ -51,10 +63,7 @@ class ForecastTimestamp:
|
|
|
51
63
|
pressure: float = field(metadata={"json_key": "seaLevelPressure"})
|
|
52
64
|
humidity: float = field(metadata={"json_key": "relativeHumidity"})
|
|
53
65
|
precipitation: float = field(metadata={"json_key": "totalPrecipitation"})
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def __post_init__(self):
|
|
57
|
-
self.condition = map_condition(self.condition_code)
|
|
66
|
+
warnings: List["WeatherWarning"] = field(default_factory=list, init=False)
|
|
58
67
|
|
|
59
68
|
|
|
60
69
|
@dataclass
|
|
@@ -123,36 +132,20 @@ def from_dict(cls, data: dict):
|
|
|
123
132
|
return cls(**init_args)
|
|
124
133
|
|
|
125
134
|
|
|
135
|
+
@dataclass
|
|
136
|
+
class WeatherWarning:
|
|
137
|
+
"""Weather Warning"""
|
|
138
|
+
|
|
139
|
+
county: str
|
|
140
|
+
warning_type: str
|
|
141
|
+
severity: str
|
|
142
|
+
description: str
|
|
143
|
+
start_time: Optional[str] = None
|
|
144
|
+
end_time: Optional[str] = None
|
|
145
|
+
|
|
146
|
+
|
|
126
147
|
Coordinates.from_dict = classmethod(from_dict)
|
|
127
148
|
Place.from_dict = classmethod(from_dict)
|
|
128
149
|
ForecastTimestamp.from_dict = classmethod(from_dict)
|
|
129
150
|
Forecast.from_dict = classmethod(from_dict)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def map_condition(api_condition):
|
|
133
|
-
"""Condition code mapping"""
|
|
134
|
-
condition_mapping = {
|
|
135
|
-
"clear": "sunny",
|
|
136
|
-
"partly-cloudy": "partlycloudy",
|
|
137
|
-
"cloudy-with-sunny-intervals": "partlycloudy",
|
|
138
|
-
"cloudy": "cloudy",
|
|
139
|
-
"thunder": "lightning",
|
|
140
|
-
"isolated-thunderstorms": "lightning-rainy",
|
|
141
|
-
"thunderstorms": "lightning-rainy",
|
|
142
|
-
"heavy-rain-with-thunderstorms": "lightning-rainy",
|
|
143
|
-
"light-rain": "rainy",
|
|
144
|
-
"rain": "rainy",
|
|
145
|
-
"heavy-rain": "pouring",
|
|
146
|
-
"light-sleet": "snowy-rainy",
|
|
147
|
-
"sleet": "snowy-rainy",
|
|
148
|
-
"freezing-rain": "snowy-rainy",
|
|
149
|
-
"hail": "hail",
|
|
150
|
-
"light-snow": "snowy",
|
|
151
|
-
"snow": "snowy",
|
|
152
|
-
"heavy-snow": "snowy",
|
|
153
|
-
"fog": "fog",
|
|
154
|
-
None: "exceptional", # For null or undefined conditions
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
# Default to 'exceptional' if the condition is not found in the mapping
|
|
158
|
-
return condition_mapping.get(api_condition, "exceptional")
|
|
151
|
+
WeatherWarning.from_dict = classmethod(from_dict)
|
meteo_lt/warnings.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""Weather warnings processor for handling warning-related logic"""
|
|
2
|
+
|
|
3
|
+
# pylint: disable=W0718
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from typing import List
|
|
8
|
+
|
|
9
|
+
from .models import Forecast, WeatherWarning
|
|
10
|
+
from .const import COUNTY_MUNICIPALITIES
|
|
11
|
+
from .client import MeteoLtClient
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class WeatherWarningsProcessor:
|
|
15
|
+
"""Processes weather warnings data and handles warning-related logic"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, client: MeteoLtClient):
|
|
18
|
+
self.client = client
|
|
19
|
+
|
|
20
|
+
async def get_weather_warnings(
|
|
21
|
+
self, administrative_division: str = None
|
|
22
|
+
) -> List[WeatherWarning]:
|
|
23
|
+
"""Fetches and processes weather warnings"""
|
|
24
|
+
warnings_data = await self.client.fetch_weather_warnings()
|
|
25
|
+
warnings = self._parse_warnings_data(warnings_data)
|
|
26
|
+
|
|
27
|
+
# Filter by administrative division if specified
|
|
28
|
+
if administrative_division:
|
|
29
|
+
warnings = [
|
|
30
|
+
w
|
|
31
|
+
for w in warnings
|
|
32
|
+
if self._warning_affects_area(w, administrative_division)
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
return warnings
|
|
36
|
+
|
|
37
|
+
def _parse_warnings_data(self, warnings_data: dict) -> List[WeatherWarning]:
|
|
38
|
+
"""Parse raw warnings data into WeatherWarning objects"""
|
|
39
|
+
warnings = []
|
|
40
|
+
|
|
41
|
+
# Handle empty response (list instead of dict)
|
|
42
|
+
if not warnings_data or isinstance(warnings_data, list):
|
|
43
|
+
return warnings
|
|
44
|
+
|
|
45
|
+
# Parse the warnings data
|
|
46
|
+
for phenomenon_group in warnings_data.get("phenomenon_groups", []):
|
|
47
|
+
# Skip hydrological warnings if needed (they're usually for water levels)
|
|
48
|
+
if phenomenon_group.get("phenomenon_category") == "hydrological":
|
|
49
|
+
continue
|
|
50
|
+
|
|
51
|
+
for area_group in phenomenon_group.get("area_groups", []):
|
|
52
|
+
for alert in area_group.get("single_alerts", []):
|
|
53
|
+
# Skip alerts with no phenomenon or empty descriptions
|
|
54
|
+
if not alert.get("phenomenon") or not alert.get(
|
|
55
|
+
"description", {}
|
|
56
|
+
).get("lt"):
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
# Create warnings for each area in the group
|
|
60
|
+
for area in area_group.get("areas", []):
|
|
61
|
+
warning = self._create_warning_from_alert(alert, area)
|
|
62
|
+
if warning:
|
|
63
|
+
warnings.append(warning)
|
|
64
|
+
|
|
65
|
+
return warnings
|
|
66
|
+
|
|
67
|
+
def _create_warning_from_alert(self, alert: dict, area: dict) -> WeatherWarning:
|
|
68
|
+
"""Create a WeatherWarning from alert data"""
|
|
69
|
+
try:
|
|
70
|
+
county = area.get("name", "Unknown")
|
|
71
|
+
phenomenon = alert.get("phenomenon", "")
|
|
72
|
+
severity = alert.get("severity", "Minor")
|
|
73
|
+
|
|
74
|
+
# Clean phenomenon name (remove severity prefixes)
|
|
75
|
+
warning_type = re.sub(r"^(dangerous|severe|extreme)-", "", phenomenon)
|
|
76
|
+
|
|
77
|
+
# Get descriptions and instructions
|
|
78
|
+
desc_dict = alert.get("description", {})
|
|
79
|
+
inst_dict = alert.get("instruction", {})
|
|
80
|
+
|
|
81
|
+
# Prefer English, fall back to Lithuanian
|
|
82
|
+
description = desc_dict.get("en") or desc_dict.get("lt", "")
|
|
83
|
+
instruction = inst_dict.get("en") or inst_dict.get("lt", "")
|
|
84
|
+
|
|
85
|
+
# Combine description and instruction
|
|
86
|
+
full_description = description
|
|
87
|
+
if instruction:
|
|
88
|
+
full_description += f"\n\nRecommendations: {instruction}"
|
|
89
|
+
|
|
90
|
+
return WeatherWarning(
|
|
91
|
+
county=county,
|
|
92
|
+
warning_type=warning_type,
|
|
93
|
+
severity=severity,
|
|
94
|
+
description=full_description,
|
|
95
|
+
start_time=alert.get("t_from"),
|
|
96
|
+
end_time=alert.get("t_to"),
|
|
97
|
+
)
|
|
98
|
+
except Exception as e:
|
|
99
|
+
print(f"Error creating warning: {e}")
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
def _warning_affects_area(
|
|
103
|
+
self, warning: WeatherWarning, administrative_division: str
|
|
104
|
+
) -> bool:
|
|
105
|
+
"""Check if warning affects specified administrative division"""
|
|
106
|
+
admin_lower = (
|
|
107
|
+
administrative_division.lower()
|
|
108
|
+
.replace(" savivaldybė", "")
|
|
109
|
+
.replace(" sav.", "")
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Check if the administrative division matches the warning county
|
|
113
|
+
if admin_lower in warning.county.lower():
|
|
114
|
+
return True
|
|
115
|
+
|
|
116
|
+
# Check if the administrative division is in the warning's county municipalities
|
|
117
|
+
if warning.county in COUNTY_MUNICIPALITIES:
|
|
118
|
+
municipalities = COUNTY_MUNICIPALITIES[warning.county]
|
|
119
|
+
for municipality in municipalities:
|
|
120
|
+
mun_clean = (
|
|
121
|
+
municipality.lower()
|
|
122
|
+
.replace(" savivaldybė", "")
|
|
123
|
+
.replace(" sav.", "")
|
|
124
|
+
)
|
|
125
|
+
if admin_lower in mun_clean or mun_clean in admin_lower:
|
|
126
|
+
return True
|
|
127
|
+
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
def enrich_forecast_with_warnings(
|
|
131
|
+
self, forecast: Forecast, warnings: List[WeatherWarning]
|
|
132
|
+
):
|
|
133
|
+
"""Enrich forecast timestamps with relevant weather warnings"""
|
|
134
|
+
if not warnings:
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
# For each forecast timestamp, find applicable warnings
|
|
138
|
+
for timestamp in forecast.forecast_timestamps:
|
|
139
|
+
timestamp.warnings = self._get_warnings_for_timestamp(
|
|
140
|
+
timestamp.datetime, warnings
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Also add warnings to current conditions if available
|
|
144
|
+
if hasattr(forecast, "current_conditions") and forecast.current_conditions:
|
|
145
|
+
forecast.current_conditions.warnings = self._get_warnings_for_timestamp(
|
|
146
|
+
forecast.current_conditions.datetime, warnings
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def _get_warnings_for_timestamp(
|
|
150
|
+
self, timestamp_str: str, warnings: List[WeatherWarning]
|
|
151
|
+
) -> List[WeatherWarning]:
|
|
152
|
+
"""Get warnings that are active for a specific timestamp"""
|
|
153
|
+
try:
|
|
154
|
+
timestamp = datetime.fromisoformat(timestamp_str).replace(
|
|
155
|
+
tzinfo=timezone.utc
|
|
156
|
+
)
|
|
157
|
+
applicable_warnings = []
|
|
158
|
+
|
|
159
|
+
for warning in warnings:
|
|
160
|
+
if not warning.start_time or not warning.end_time:
|
|
161
|
+
continue
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
start_time = datetime.fromisoformat(
|
|
165
|
+
warning.start_time.replace("Z", "+00:00")
|
|
166
|
+
)
|
|
167
|
+
end_time = datetime.fromisoformat(
|
|
168
|
+
warning.end_time.replace("Z", "+00:00")
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Check if timestamp falls within warning period
|
|
172
|
+
if start_time <= timestamp <= end_time:
|
|
173
|
+
applicable_warnings.append(warning)
|
|
174
|
+
|
|
175
|
+
except (ValueError, AttributeError):
|
|
176
|
+
# Skip warnings with invalid time formats
|
|
177
|
+
continue
|
|
178
|
+
|
|
179
|
+
return applicable_warnings
|
|
180
|
+
|
|
181
|
+
except (ValueError, AttributeError):
|
|
182
|
+
# Return empty list if timestamp parsing fails
|
|
183
|
+
return []
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: meteo_lt-pkg
|
|
3
|
+
Version: 0.4.0b1
|
|
4
|
+
Summary: A library to fetch weather data from api.meteo.lt
|
|
5
|
+
Author-email: Brunas <brunonas@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/Brunas/meteo_lt-pkg
|
|
7
|
+
Project-URL: Issues, https://github.com/Brunas/meteo_lt-pkg/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: aiohttp
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: pytest; extra == "dev"
|
|
18
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
19
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
20
|
+
Requires-Dist: black; extra == "dev"
|
|
21
|
+
Requires-Dist: coverage; extra == "dev"
|
|
22
|
+
Requires-Dist: flake8; extra == "dev"
|
|
23
|
+
Requires-Dist: pyflakes; extra == "dev"
|
|
24
|
+
Requires-Dist: pylint; extra == "dev"
|
|
25
|
+
Requires-Dist: build; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# Meteo.Lt Lithuanian weather forecast package
|
|
29
|
+
|
|
30
|
+
[![GitHub Release][releases-shield]][releases]
|
|
31
|
+
[![GitHub Activity][commits-shield]][commits]
|
|
32
|
+
[![License][license-shield]](LICENSE)
|
|
33
|
+
![Project Maintenance][maintenance-shield]
|
|
34
|
+
[](https://github.com/psf/black)
|
|
35
|
+
|
|
36
|
+
<a href="https://buymeacoffee.com/pdfdc52z8h" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 40px !important;width: 145px !important;" ></a>
|
|
37
|
+
|
|
38
|
+
MeteoLt-Pkg is a Python library designed to fetch weather data from [`api.meteo.lt`](https://api.meteo.lt/). This library provides convenient methods to interact with the API and obtain weather forecasts and related data. Please visit for more information.
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
You can install the package using pip:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install meteo_lt-pkg
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quick Start
|
|
49
|
+
|
|
50
|
+
Here's a quick example to get you started:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
import asyncio
|
|
54
|
+
from meteo_lt import MeteoLtAPI
|
|
55
|
+
|
|
56
|
+
async def quick_example():
|
|
57
|
+
async with MeteoLtAPI() as api:
|
|
58
|
+
# Get current weather for Vilnius
|
|
59
|
+
forecast = await api.get_forecast("vilnius")
|
|
60
|
+
current = forecast.current_conditions
|
|
61
|
+
|
|
62
|
+
print(f"Current temperature in Vilnius: {current.temperature}°C")
|
|
63
|
+
print(f"Condition: {current.condition_code}")
|
|
64
|
+
print(f"Wind: {current.wind_speed} m/s")
|
|
65
|
+
|
|
66
|
+
# Check for weather warnings
|
|
67
|
+
warnings = await api.get_weather_warnings("Vilniaus miesto")
|
|
68
|
+
if warnings:
|
|
69
|
+
print(f"Active warnings: {len(warnings)}")
|
|
70
|
+
for warning in warnings:
|
|
71
|
+
print(f" - {warning.warning_type}: {warning.severity}")
|
|
72
|
+
else:
|
|
73
|
+
print("No active weather warnings")
|
|
74
|
+
|
|
75
|
+
asyncio.run(quick_example())
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Usage
|
|
79
|
+
|
|
80
|
+
### Basic Usage (Recommended)
|
|
81
|
+
|
|
82
|
+
The recommended way to use the library is with the `async with` context manager, which ensures proper cleanup of HTTP sessions:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
import asyncio
|
|
86
|
+
from meteo_lt import MeteoLtAPI
|
|
87
|
+
|
|
88
|
+
async def main():
|
|
89
|
+
async with MeteoLtAPI() as api:
|
|
90
|
+
# Get weather forecast for Vilnius
|
|
91
|
+
forecast = await api.get_forecast("vilnius")
|
|
92
|
+
print(f"Current temperature in {forecast.place.name}: {forecast.current_conditions.temperature}°C")
|
|
93
|
+
|
|
94
|
+
# Get weather warnings for Vilnius
|
|
95
|
+
warnings = await api.get_weather_warnings("Vilniaus miesto")
|
|
96
|
+
print(f"Active warnings: {len(warnings)}")
|
|
97
|
+
for warning in warnings:
|
|
98
|
+
print(f"- {warning.warning_type}: {warning.description}")
|
|
99
|
+
|
|
100
|
+
asyncio.run(main())
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Alternative Usage
|
|
104
|
+
|
|
105
|
+
If you prefer not to use the context manager, make sure to call `close()` to properly cleanup resources:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
async def alternative_usage():
|
|
109
|
+
api = MeteoLtAPI()
|
|
110
|
+
try:
|
|
111
|
+
forecast = await api.get_forecast("kaunas")
|
|
112
|
+
print(f"Temperature: {forecast.current_conditions.temperature}°C")
|
|
113
|
+
finally:
|
|
114
|
+
await api.close() # Important: prevents session warnings
|
|
115
|
+
|
|
116
|
+
asyncio.run(alternative_usage())
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Fetching Places
|
|
120
|
+
|
|
121
|
+
To get the list of available places:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
async def fetch_places():
|
|
125
|
+
async with MeteoLtAPI() as api:
|
|
126
|
+
await api.fetch_places()
|
|
127
|
+
for place in api.places:
|
|
128
|
+
print(f"{place.name} ({place.code})")
|
|
129
|
+
|
|
130
|
+
asyncio.run(fetch_places())
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Getting the Nearest Place
|
|
134
|
+
|
|
135
|
+
You can find the nearest place using latitude and longitude coordinates:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
async def find_nearest_place():
|
|
139
|
+
async with MeteoLtAPI() as api:
|
|
140
|
+
# Example coordinates for Vilnius, Lithuania
|
|
141
|
+
nearest_place = await api.get_nearest_place(54.6872, 25.2797)
|
|
142
|
+
print(f"Nearest place: {nearest_place.name}")
|
|
143
|
+
|
|
144
|
+
asyncio.run(find_nearest_place())
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
> **NOTE**: If no places are retrieved before, that is done automatically in `get_nearest_place` method.
|
|
148
|
+
|
|
149
|
+
### Fetching Weather Forecast
|
|
150
|
+
|
|
151
|
+
To get the weather forecast for a specific place:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
async def fetch_forecast():
|
|
155
|
+
async with MeteoLtAPI() as api:
|
|
156
|
+
# Get forecast for Vilnius
|
|
157
|
+
forecast = await api.get_forecast("vilnius")
|
|
158
|
+
|
|
159
|
+
# Current conditions
|
|
160
|
+
current = forecast.current_conditions
|
|
161
|
+
print(f"Current temperature: {current.temperature}°C")
|
|
162
|
+
print(f"Feels like: {current.apparent_temperature}°C")
|
|
163
|
+
print(f"Condition: {current.condition_code}")
|
|
164
|
+
|
|
165
|
+
# Future forecasts
|
|
166
|
+
print(f"\nNext 24 hours:")
|
|
167
|
+
for timestamp in forecast.forecast_timestamps[:24]:
|
|
168
|
+
print(f"{timestamp.datetime}: {timestamp.temperature}°C")
|
|
169
|
+
|
|
170
|
+
asyncio.run(fetch_forecast())
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
> **NOTE**: `current_conditions` is the current hour record from the `forecast_timestamps` array. Also, `forecast_timestamps` array has past time records filtered out due to `api.meteo.lt` not doing that automatically.
|
|
174
|
+
|
|
175
|
+
### Fetching Weather Forecast with Warnings
|
|
176
|
+
|
|
177
|
+
To get weather forecast enriched with warnings:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
async def fetch_forecast_with_warnings():
|
|
181
|
+
async with MeteoLtAPI() as api:
|
|
182
|
+
# Get forecast with warnings using coordinates
|
|
183
|
+
forecast = await api.get_forecast_with_warnings(
|
|
184
|
+
latitude=54.6872,
|
|
185
|
+
longitude=25.2797
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
print(f"Forecast for {forecast.place.name}")
|
|
189
|
+
print(f"Current temperature: {forecast.current_conditions.temperature}°C")
|
|
190
|
+
|
|
191
|
+
# Check for warnings in current conditions
|
|
192
|
+
if forecast.current_conditions.warnings:
|
|
193
|
+
print("Current warnings:")
|
|
194
|
+
for warning in forecast.current_conditions.warnings:
|
|
195
|
+
print(f"- {warning.warning_type}: {warning.severity}")
|
|
196
|
+
|
|
197
|
+
asyncio.run(fetch_forecast_with_warnings())
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Fetching Weather Warnings
|
|
201
|
+
|
|
202
|
+
To get weather warnings for Lithuania or specific administrative areas:
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
async def fetch_warnings():
|
|
206
|
+
async with MeteoLtAPI() as api:
|
|
207
|
+
# Get all weather warnings
|
|
208
|
+
warnings = await api.get_weather_warnings()
|
|
209
|
+
print(f"Total active warnings: {len(warnings)}")
|
|
210
|
+
|
|
211
|
+
for warning in warnings:
|
|
212
|
+
print(f"Warning: {warning.warning_type} in {warning.county}")
|
|
213
|
+
print(f"Severity: {warning.severity}")
|
|
214
|
+
print(f"Description: {warning.description}")
|
|
215
|
+
print(f"Active: {warning.start_time} to {warning.end_time}")
|
|
216
|
+
print("-" * 50)
|
|
217
|
+
|
|
218
|
+
async def fetch_warnings_for_area():
|
|
219
|
+
async with MeteoLtAPI() as api:
|
|
220
|
+
# Get warnings for specific administrative division
|
|
221
|
+
vilnius_warnings = await api.get_weather_warnings("Vilniaus miesto")
|
|
222
|
+
print(f"Warnings for Vilnius: {len(vilnius_warnings)}")
|
|
223
|
+
|
|
224
|
+
for warning in vilnius_warnings:
|
|
225
|
+
print(f"- {warning.warning_type} ({warning.severity})")
|
|
226
|
+
|
|
227
|
+
asyncio.run(fetch_warnings())
|
|
228
|
+
asyncio.run(fetch_warnings_for_area())
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Data Models
|
|
232
|
+
|
|
233
|
+
The package includes several data models to represent the API responses:
|
|
234
|
+
|
|
235
|
+
### Coordinates
|
|
236
|
+
|
|
237
|
+
Represents geographic coordinates.
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
from meteo_lt import Coordinates
|
|
241
|
+
|
|
242
|
+
coords = Coordinates(latitude=54.6872, longitude=25.2797)
|
|
243
|
+
print(coords)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Place
|
|
247
|
+
|
|
248
|
+
Represents a place with associated metadata.
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
from meteo_lt import Place
|
|
252
|
+
|
|
253
|
+
place = Place(code="vilnius", name="Vilnius", administrative_division="Vilnius City Municipality", country="LT", coordinates=coords)
|
|
254
|
+
print(place.latitude, place.longitude)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### ForecastTimestamp
|
|
258
|
+
|
|
259
|
+
Represents a timestamp within the weather forecast, including various weather parameters.
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
from meteo_lt import ForecastTimestamp
|
|
263
|
+
|
|
264
|
+
forecast_timestamp = ForecastTimestamp(
|
|
265
|
+
datetime="2024-07-23T12:00:00+00:00",
|
|
266
|
+
temperature=25.5,
|
|
267
|
+
apparent_temperature=27.0,
|
|
268
|
+
condition_code="clear",
|
|
269
|
+
wind_speed=5.0,
|
|
270
|
+
wind_gust_speed=8.0,
|
|
271
|
+
wind_bearing=180,
|
|
272
|
+
cloud_coverage=20,
|
|
273
|
+
pressure=1012,
|
|
274
|
+
humidity=60,
|
|
275
|
+
precipitation=0
|
|
276
|
+
)
|
|
277
|
+
print(forecast_timestamp.condition)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Forecast
|
|
281
|
+
|
|
282
|
+
Represents the weather forecast for a place, containing multiple forecast timestamps.
|
|
283
|
+
|
|
284
|
+
```python
|
|
285
|
+
from meteo_lt import Forecast
|
|
286
|
+
|
|
287
|
+
forecast = Forecast(
|
|
288
|
+
place=place,
|
|
289
|
+
forecast_created=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
290
|
+
forecast_timestamps=[forecast_timestamp]
|
|
291
|
+
)
|
|
292
|
+
print(forecast.current_conditions().temperature)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### WeatherWarning
|
|
296
|
+
|
|
297
|
+
Represents a weather warning for a specific area.
|
|
298
|
+
|
|
299
|
+
```python
|
|
300
|
+
from meteo_lt import WeatherWarning
|
|
301
|
+
|
|
302
|
+
warning = WeatherWarning(
|
|
303
|
+
county="Vilniaus apskritis",
|
|
304
|
+
warning_type="frost",
|
|
305
|
+
severity="Moderate",
|
|
306
|
+
description="Ground surface frost 0-5 degrees in many places",
|
|
307
|
+
start_time="2024-07-23T12:00:00Z",
|
|
308
|
+
end_time="2024-07-23T18:00:00Z"
|
|
309
|
+
)
|
|
310
|
+
print(f"Warning for {warning.county}: {warning.description}")
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Contributing
|
|
314
|
+
|
|
315
|
+
Contributions are welcome! For major changes please open an issue to discuss or submit a pull request with your changes. If you want to contribute you can use devcontainers in vscode for easiest setup follow [instructions here](.devcontainer/README.md).
|
|
316
|
+
|
|
317
|
+
***
|
|
318
|
+
|
|
319
|
+
[commits-shield]: https://img.shields.io/github/commit-activity/y/Brunas/meteo_lt-pkg.svg?style=flat-square
|
|
320
|
+
[commits]: https://github.com/Brunas/meteo_lt-pkg/commits/main
|
|
321
|
+
[license-shield]: https://img.shields.io/github/license/Brunas/meteo_lt-pkg.svg?style=flat-square
|
|
322
|
+
[maintenance-shield]: https://img.shields.io/badge/maintainer-Brunas%20%40Brunas-blue.svg?style=flat-square
|
|
323
|
+
[releases-shield]: https://img.shields.io/github/release/Brunas/meteo_lt-pkg.svg?style=flat-square
|
|
324
|
+
[releases]: https://github.com/Brunas/meteo_lt-pkg/releases
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
meteo_lt/__init__.py,sha256=TP3DhEXEpHdI6bevRjxb0cbVrEWbI8mEocYc1DwA_KI,255
|
|
2
|
+
meteo_lt/api.py,sha256=ouM8zB51CuJVm3NmT6uuRMJsvuu49fmFMGyFzy3UJyM,3202
|
|
3
|
+
meteo_lt/client.py,sha256=j5F1Ag00EnEI8ErLz0Q2lYqWfO8YgqYlsAHfhzkGyWE,2798
|
|
4
|
+
meteo_lt/const.py,sha256=1xeQ2wErqPREnYfK4OogadlVIu0p2gGoILsOcsIfp8Q,2702
|
|
5
|
+
meteo_lt/models.py,sha256=xLx9DcrXOolED8PPQ2fcu4dNdPA6ykS9c_zherLTCj8,4840
|
|
6
|
+
meteo_lt/utils.py,sha256=SL7ZeTEfdrdQoe_DOJaiA2zVxt08ujyFJ24JxJm1Hks,1081
|
|
7
|
+
meteo_lt/warnings.py,sha256=YthSCVDAJgzxHwtZ6jZmnImq1ZZsj2Nd9y9RKMPZoG0,6954
|
|
8
|
+
meteo_lt_pkg-0.4.0b1.dist-info/licenses/LICENSE,sha256=3IGi6xn6NUdXGvcdwD0MUbhy3Yz5NRnUjJrwKanFAD4,1073
|
|
9
|
+
meteo_lt_pkg-0.4.0b1.dist-info/METADATA,sha256=yv1prhcoQzP8lug8rEbBf47W1twMj8NaINxN5eFOcBI,10330
|
|
10
|
+
meteo_lt_pkg-0.4.0b1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
11
|
+
meteo_lt_pkg-0.4.0b1.dist-info/top_level.txt,sha256=-aEdc9FzHhcIH4_0TNdKNxuvDnS3chKoJy6LK9Ud-G4,9
|
|
12
|
+
meteo_lt_pkg-0.4.0b1.dist-info/RECORD,,
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.2
|
|
2
|
-
Name: meteo_lt-pkg
|
|
3
|
-
Version: 0.2.4
|
|
4
|
-
Summary: A library to fetch weather data from api.meteo.lt
|
|
5
|
-
Author-email: Brunas <brunonas@gmail.com>
|
|
6
|
-
Project-URL: Homepage, https://github.com/Brunas/meteo_lt-pkg
|
|
7
|
-
Project-URL: Issues, https://github.com/Brunas/meteo_lt-pkg/issues
|
|
8
|
-
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Classifier: Operating System :: OS Independent
|
|
11
|
-
Classifier: Development Status :: 4 - Beta
|
|
12
|
-
Requires-Python: >=3.10
|
|
13
|
-
Description-Content-Type: text/markdown
|
|
14
|
-
License-File: LICENSE
|
|
15
|
-
Requires-Dist: aiohttp
|
|
16
|
-
Provides-Extra: dev
|
|
17
|
-
Requires-Dist: pytest; extra == "dev"
|
|
18
|
-
Requires-Dist: pytest-cov; extra == "dev"
|
|
19
|
-
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
20
|
-
Requires-Dist: black; extra == "dev"
|
|
21
|
-
Requires-Dist: coverage; extra == "dev"
|
|
22
|
-
Requires-Dist: flake8; extra == "dev"
|
|
23
|
-
Requires-Dist: pyflakes; extra == "dev"
|
|
24
|
-
Requires-Dist: pylint; extra == "dev"
|
|
25
|
-
Requires-Dist: build; extra == "dev"
|
|
26
|
-
|
|
27
|
-
# Meteo.Lt Lithuanian weather forecast package
|
|
28
|
-
|
|
29
|
-
[![GitHub Release][releases-shield]][releases]
|
|
30
|
-
[![GitHub Activity][commits-shield]][commits]
|
|
31
|
-
[![License][license-shield]](LICENSE)
|
|
32
|
-
![Project Maintenance][maintenance-shield]
|
|
33
|
-
[](https://github.com/psf/black)
|
|
34
|
-
|
|
35
|
-
<a href="https://buymeacoffee.com/pdfdc52z8h" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 40px !important;width: 145px !important;" ></a>
|
|
36
|
-
|
|
37
|
-
MeteoLt-Pkg is a Python library designed to fetch weather data from [`api.meteo.lt`](https://api.meteo.lt/). This library provides convenient methods to interact with the API and obtain weather forecasts and related data. Please visit for more information.
|
|
38
|
-
|
|
39
|
-
## Installation
|
|
40
|
-
|
|
41
|
-
You can install the package using pip:
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
pip install meteo_lt-pkg
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
## Usage
|
|
48
|
-
|
|
49
|
-
Initializing the API Client
|
|
50
|
-
To start using the library, you need to initialize the `MeteoLtAPI` client:
|
|
51
|
-
|
|
52
|
-
```python
|
|
53
|
-
from meteo_lt import MeteoLtAPI
|
|
54
|
-
|
|
55
|
-
api_client = MeteoLtAPI()
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### Fetching Places
|
|
59
|
-
|
|
60
|
-
To get the list of available places:
|
|
61
|
-
|
|
62
|
-
```python
|
|
63
|
-
import asyncio
|
|
64
|
-
|
|
65
|
-
async def fetch_places():
|
|
66
|
-
await api_client.fetch_places()
|
|
67
|
-
for place in api_client.places:
|
|
68
|
-
print(place)
|
|
69
|
-
|
|
70
|
-
asyncio.run(fetch_places())
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### Getting the Nearest Place
|
|
74
|
-
|
|
75
|
-
You can find the nearest place using latitude and longitude coordinates:
|
|
76
|
-
|
|
77
|
-
```python
|
|
78
|
-
async def find_nearest_place(latitude, longitude):
|
|
79
|
-
nearest_place = await api_client.get_nearest_place(latitude, longitude)
|
|
80
|
-
print(f"Nearest place: {nearest_place.name}")
|
|
81
|
-
|
|
82
|
-
# Example coordinates for Vilnius, Lithuania
|
|
83
|
-
asyncio.run(find_nearest_place(54.6872, 25.2797))
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
Also, if no places are retrieved before, that is done automatically in `get_nearest_place` method.
|
|
87
|
-
|
|
88
|
-
### Fetching Weather Forecast
|
|
89
|
-
|
|
90
|
-
To get the weather forecast for a specific place, use the get_forecast method with the place code:
|
|
91
|
-
|
|
92
|
-
```python
|
|
93
|
-
async def fetch_forecast(place_code):
|
|
94
|
-
forecast = await api_client.get_forecast(place_code)
|
|
95
|
-
current_conditions = forecast.current_conditions()
|
|
96
|
-
print(f"Current temperature: {current_conditions.temperature}°C")
|
|
97
|
-
|
|
98
|
-
# Example place code for Vilnius, Lithuania
|
|
99
|
-
asyncio.run(fetch_forecast("vilnius"))
|
|
100
|
-
```
|
|
101
|
-
>**NOTE** `current_conditions` is the current hour record from the `forecast_timestamps` array. Also, `forecast_timestamps` array has past time records filtered out due to `api.meteo.lt` not doing that automatically.
|
|
102
|
-
|
|
103
|
-
## Data Models
|
|
104
|
-
|
|
105
|
-
The package includes several data models to represent the API responses:
|
|
106
|
-
|
|
107
|
-
### Coordinates
|
|
108
|
-
|
|
109
|
-
Represents geographic coordinates.
|
|
110
|
-
|
|
111
|
-
```python
|
|
112
|
-
from meteo_lt import Coordinates
|
|
113
|
-
|
|
114
|
-
coords = Coordinates(latitude=54.6872, longitude=25.2797)
|
|
115
|
-
print(coords)
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### Place
|
|
119
|
-
|
|
120
|
-
Represents a place with associated metadata.
|
|
121
|
-
|
|
122
|
-
```python
|
|
123
|
-
from meteo_lt import Place
|
|
124
|
-
|
|
125
|
-
place = Place(code="vilnius", name="Vilnius", administrative_division="Vilnius City Municipality", country="LT", coordinates=coords)
|
|
126
|
-
print(place.latitude, place.longitude)
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### ForecastTimestamp
|
|
130
|
-
|
|
131
|
-
Represents a timestamp within the weather forecast, including various weather parameters.
|
|
132
|
-
|
|
133
|
-
```python
|
|
134
|
-
from meteo_lt import ForecastTimestamp
|
|
135
|
-
|
|
136
|
-
forecast_timestamp = ForecastTimestamp(
|
|
137
|
-
datetime="2024-07-23T12:00:00+00:00",
|
|
138
|
-
temperature=25.5,
|
|
139
|
-
apparent_temperature=27.0,
|
|
140
|
-
condition_code="clear",
|
|
141
|
-
wind_speed=5.0,
|
|
142
|
-
wind_gust_speed=8.0,
|
|
143
|
-
wind_bearing=180,
|
|
144
|
-
cloud_coverage=20,
|
|
145
|
-
pressure=1012,
|
|
146
|
-
humidity=60,
|
|
147
|
-
precipitation=0
|
|
148
|
-
)
|
|
149
|
-
print(forecast_timestamp.condition)
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### Forecast
|
|
153
|
-
|
|
154
|
-
Represents the weather forecast for a place, containing multiple forecast timestamps.
|
|
155
|
-
|
|
156
|
-
```python
|
|
157
|
-
from meteo_lt import Forecast
|
|
158
|
-
|
|
159
|
-
forecast = Forecast(
|
|
160
|
-
place=place,
|
|
161
|
-
forecast_created=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
162
|
-
forecast_timestamps=[forecast_timestamp]
|
|
163
|
-
)
|
|
164
|
-
print(forecast.current_conditions().temperature)
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
## Contributing
|
|
168
|
-
|
|
169
|
-
Contributions are welcome! For major changes please open an issue to discuss or submit a pull request with your changes. If you want to contribute you can use devcontainers in vscode for easiest setup follow [instructions here](.devcontainer/README.md).
|
|
170
|
-
|
|
171
|
-
***
|
|
172
|
-
|
|
173
|
-
[commits-shield]: https://img.shields.io/github/commit-activity/y/Brunas/meteo_lt-pkg.svg?style=flat-square
|
|
174
|
-
[commits]: https://github.com/Brunas/meteo_lt-pkg/commits/main
|
|
175
|
-
[license-shield]: https://img.shields.io/github/license/Brunas/meteo_lt-pkg.svg?style=flat-square
|
|
176
|
-
[maintenance-shield]: https://img.shields.io/badge/maintainer-Brunas%20%40Brunas-blue.svg?style=flat-square
|
|
177
|
-
[releases-shield]: https://img.shields.io/github/release/Brunas/meteo_lt-pkg.svg?style=flat-square
|
|
178
|
-
[releases]: https://github.com/Brunas/meteo_lt-pkg/releases
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
meteo_lt/__init__.py,sha256=3RXg8MWNyXDJ4PpifsXRAT7t1mRNAJwSFlqJBmLeoPk,194
|
|
2
|
-
meteo_lt/api.py,sha256=oFpIqdOmVcSUdaMrnlbGV1k5w1XIdIJsKroXkczz8JE,1620
|
|
3
|
-
meteo_lt/models.py,sha256=brL4d0Mo4Fg29tKd13lxINygLL6EOQZJj6jOXrbP9Z0,5148
|
|
4
|
-
meteo_lt/utils.py,sha256=SL7ZeTEfdrdQoe_DOJaiA2zVxt08ujyFJ24JxJm1Hks,1081
|
|
5
|
-
meteo_lt_pkg-0.2.4.dist-info/LICENSE,sha256=3IGi6xn6NUdXGvcdwD0MUbhy3Yz5NRnUjJrwKanFAD4,1073
|
|
6
|
-
meteo_lt_pkg-0.2.4.dist-info/METADATA,sha256=vmOqwXFTRmqdyRG7VdRBl4TBc6Rau71cz3FiOqDHPdQ,5670
|
|
7
|
-
meteo_lt_pkg-0.2.4.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
8
|
-
meteo_lt_pkg-0.2.4.dist-info/top_level.txt,sha256=-aEdc9FzHhcIH4_0TNdKNxuvDnS3chKoJy6LK9Ud-G4,9
|
|
9
|
-
meteo_lt_pkg-0.2.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|