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 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__ = ["MeteoLtAPI", "Coordinates", "Place", "ForecastTimestamp", "Forecast"]
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
- import aiohttp
3
+ # pylint: disable=W0718
4
4
 
5
- from .models import Place, Forecast
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
- async with aiohttp.ClientSession(
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 get_forecast(self, place_code):
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
- async with aiohttp.ClientSession(
39
- timeout=aiohttp.ClientTimeout(total=self.TIMEOUT)
40
- ) as session:
41
- async with session.get(
42
- f"{self.BASE_URL}/places/{place_code}/forecasts/long-term"
43
- ) as response:
44
- response.raise_for_status()
45
- response.encoding = self.ENCODING
46
- response_json = await response.json()
47
- return Forecast.from_dict(response_json)
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
- country: str
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
- condition: str = field(init=False)
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
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](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,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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
- [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](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,,