meteo-lt-pkg 0.3.0__py3-none-any.whl → 0.4.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.
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,37 @@
1
1
  """Main API class script"""
2
2
 
3
- import aiohttp
3
+ from typing import List
4
4
 
5
- from .models import Place, Forecast
5
+ from .models import Forecast, WeatherWarning
6
6
  from .utils import find_nearest_place
7
+ from .client import MeteoLtClient
8
+ from .warnings import WeatherWarningsProcessor
7
9
 
8
10
 
9
11
  class MeteoLtAPI:
10
- """Main API class"""
12
+ """Main API class that orchestrates external API calls and warning processing"""
11
13
 
12
- BASE_URL = "https://api.meteo.lt/v1"
13
- TIMEOUT = 30
14
- ENCODING = "utf-8"
15
-
16
- def __init__(self):
14
+ def __init__(self, session=None):
17
15
  self.places = []
16
+ self.client = MeteoLtClient(session)
17
+ self.warnings_processor = WeatherWarningsProcessor(self.client)
18
+
19
+ async def __aenter__(self):
20
+ """Async context manager entry"""
21
+ await self.client.__aenter__()
22
+ return self
23
+
24
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
25
+ """Async context manager exit"""
26
+ await self.client.__aexit__(exc_type, exc_val, exc_tb)
27
+
28
+ async def close(self):
29
+ """Close the API client and cleanup resources"""
30
+ await self.client.close()
18
31
 
19
32
  async def fetch_places(self):
20
33
  """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]
34
+ self.places = await self.client.fetch_places()
29
35
 
30
36
  async def get_nearest_place(self, latitude, longitude):
31
37
  """Finds nearest place using provided coordinates"""
@@ -33,15 +39,49 @@ class MeteoLtAPI:
33
39
  await self.fetch_places()
34
40
  return find_nearest_place(latitude, longitude, self.places)
35
41
 
36
- async def get_forecast(self, place_code):
42
+ async def get_forecast_with_warnings(
43
+ self, latitude=None, longitude=None, place_code=None
44
+ ):
45
+ """Get forecast with weather warnings for a location"""
46
+ if place_code is None:
47
+ if latitude is None or longitude is None:
48
+ raise ValueError(
49
+ "Either place_code or both latitude and longitude must be provided"
50
+ )
51
+ place = await self.get_nearest_place(latitude, longitude)
52
+ place_code = place.code
53
+
54
+ return await self.get_forecast(place_code, include_warnings=True)
55
+
56
+ async def get_forecast(self, place_code, include_warnings=True):
37
57
  """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)
58
+ forecast = await self.client.fetch_forecast(place_code)
59
+
60
+ if include_warnings:
61
+ await self._enrich_forecast_with_warnings(forecast)
62
+
63
+ return forecast
64
+
65
+ async def get_weather_warnings(
66
+ self, administrative_division: str = None
67
+ ) -> List[WeatherWarning]:
68
+ """Fetches weather warnings from meteo.lt JSON API"""
69
+ return await self.warnings_processor.get_weather_warnings(
70
+ administrative_division
71
+ )
72
+
73
+ async def _enrich_forecast_with_warnings(self, forecast: Forecast):
74
+ """Enrich forecast timestamps with relevant weather warnings"""
75
+ if (
76
+ not forecast
77
+ or not forecast.place
78
+ or not forecast.place.administrative_division
79
+ ):
80
+ return
81
+
82
+ warnings = await self.get_weather_warnings(
83
+ forecast.place.administrative_division
84
+ )
85
+
86
+ if warnings:
87
+ self.warnings_processor.enrich_forecast_with_warnings(forecast, warnings)
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,27 @@ 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
+
50
+
51
+ @dataclass
52
+ class WeatherWarning:
53
+ """Weather Warning"""
54
+
55
+ county: str
56
+ warning_type: str
57
+ severity: str
58
+ description: str
59
+ start_time: Optional[str] = None
60
+ end_time: Optional[str] = None
61
+
38
62
 
39
63
  @dataclass
40
64
  class ForecastTimestamp:
@@ -51,6 +75,7 @@ class ForecastTimestamp:
51
75
  pressure: float = field(metadata={"json_key": "seaLevelPressure"})
52
76
  humidity: float = field(metadata={"json_key": "relativeHumidity"})
53
77
  precipitation: float = field(metadata={"json_key": "totalPrecipitation"})
78
+ warnings: List[WeatherWarning] = field(default_factory=list, init=False)
54
79
 
55
80
 
56
81
  @dataclass
@@ -123,3 +148,4 @@ Coordinates.from_dict = classmethod(from_dict)
123
148
  Place.from_dict = classmethod(from_dict)
124
149
  ForecastTimestamp.from_dict = classmethod(from_dict)
125
150
  Forecast.from_dict = classmethod(from_dict)
151
+ WeatherWarning.from_dict = classmethod(from_dict)
meteo_lt/warnings.py ADDED
@@ -0,0 +1,173 @@
1
+ """Weather warnings processor for handling warning-related logic"""
2
+
3
+ import re
4
+ from datetime import datetime, timezone
5
+ from typing import List
6
+
7
+ from .models import Forecast, WeatherWarning
8
+ from .const import COUNTY_MUNICIPALITIES
9
+ from .client import MeteoLtClient
10
+
11
+
12
+ class WeatherWarningsProcessor:
13
+ """Processes weather warnings data and handles warning-related logic"""
14
+
15
+ def __init__(self, client: MeteoLtClient):
16
+ self.client = client
17
+
18
+ async def get_weather_warnings(
19
+ self, administrative_division: str = None
20
+ ) -> List[WeatherWarning]:
21
+ """Fetches and processes weather warnings"""
22
+ warnings_data = await self.client.fetch_weather_warnings()
23
+ warnings = self._parse_warnings_data(warnings_data)
24
+
25
+ # Filter by administrative division if specified
26
+ if administrative_division:
27
+ warnings = [
28
+ w
29
+ for w in warnings
30
+ if self._warning_affects_area(w, administrative_division)
31
+ ]
32
+
33
+ return warnings
34
+
35
+ def _parse_warnings_data(self, warnings_data: dict) -> List[WeatherWarning]:
36
+ """Parse raw warnings data into WeatherWarning objects"""
37
+ warnings = []
38
+
39
+ # Handle empty response (list instead of dict)
40
+ if not warnings_data or isinstance(warnings_data, list):
41
+ return warnings
42
+
43
+ # Parse the warnings data
44
+ for phenomenon_group in warnings_data.get("phenomenon_groups", []):
45
+ # Skip hydrological warnings if needed (they're usually for water levels)
46
+ if phenomenon_group.get("phenomenon_category") == "hydrological":
47
+ continue
48
+
49
+ for area_group in phenomenon_group.get("area_groups", []):
50
+ for alert in area_group.get("single_alerts", []):
51
+ # Skip alerts with no phenomenon or empty descriptions
52
+ if not alert.get("phenomenon") or not alert.get(
53
+ "description", {}
54
+ ).get("lt"):
55
+ continue
56
+
57
+ # Create warnings for each area in the group
58
+ for area in area_group.get("areas", []):
59
+ warning = self._create_warning_from_alert(alert, area)
60
+ if warning:
61
+ warnings.append(warning)
62
+
63
+ return warnings
64
+
65
+ def _create_warning_from_alert(self, alert: dict, area: dict) -> WeatherWarning:
66
+ """Create a WeatherWarning from alert data"""
67
+ county = area.get("name", "Unknown")
68
+ phenomenon = alert.get("phenomenon", "")
69
+ severity = alert.get("severity", "Minor")
70
+
71
+ warning_type = re.sub(r"^(dangerous|severe|extreme)-", "", phenomenon)
72
+
73
+ desc_dict = alert.get("description", {})
74
+ inst_dict = alert.get("instruction", {})
75
+
76
+ description = desc_dict.get("en") or desc_dict.get("lt", "")
77
+ instruction = inst_dict.get("en") or inst_dict.get("lt", "")
78
+
79
+ full_description = description
80
+ if instruction:
81
+ full_description += f"\n\nRecommendations: {instruction}"
82
+
83
+ return WeatherWarning(
84
+ county=county,
85
+ warning_type=warning_type,
86
+ severity=severity,
87
+ description=full_description,
88
+ start_time=alert.get("t_from"),
89
+ end_time=alert.get("t_to"),
90
+ )
91
+
92
+ def _warning_affects_area(
93
+ self, warning: WeatherWarning, administrative_division: str
94
+ ) -> bool:
95
+ """Check if warning affects specified administrative division"""
96
+ admin_lower = (
97
+ administrative_division.lower()
98
+ .replace(" savivaldybė", "")
99
+ .replace(" sav.", "")
100
+ )
101
+
102
+ # Check if the administrative division matches the warning county
103
+ if admin_lower in warning.county.lower():
104
+ return True
105
+
106
+ # Check if the administrative division is in the warning's county municipalities
107
+ if warning.county in COUNTY_MUNICIPALITIES:
108
+ municipalities = COUNTY_MUNICIPALITIES[warning.county]
109
+ for municipality in municipalities:
110
+ mun_clean = (
111
+ municipality.lower()
112
+ .replace(" savivaldybė", "")
113
+ .replace(" sav.", "")
114
+ )
115
+ if admin_lower in mun_clean or mun_clean in admin_lower:
116
+ return True
117
+
118
+ return False
119
+
120
+ def enrich_forecast_with_warnings(
121
+ self, forecast: Forecast, warnings: List[WeatherWarning]
122
+ ):
123
+ """Enrich forecast timestamps with relevant weather warnings"""
124
+ if not warnings:
125
+ return
126
+
127
+ # For each forecast timestamp, find applicable warnings
128
+ for timestamp in forecast.forecast_timestamps:
129
+ timestamp.warnings = self._get_warnings_for_timestamp(
130
+ timestamp.datetime, warnings
131
+ )
132
+
133
+ # Also add warnings to current conditions if available
134
+ if hasattr(forecast, "current_conditions") and forecast.current_conditions:
135
+ forecast.current_conditions.warnings = self._get_warnings_for_timestamp(
136
+ forecast.current_conditions.datetime, warnings
137
+ )
138
+
139
+ def _get_warnings_for_timestamp(
140
+ self, timestamp_str: str, warnings: List[WeatherWarning]
141
+ ) -> List[WeatherWarning]:
142
+ """Get warnings that are active for a specific timestamp"""
143
+ try:
144
+ timestamp = datetime.fromisoformat(timestamp_str).replace(
145
+ tzinfo=timezone.utc
146
+ )
147
+ applicable_warnings = []
148
+
149
+ for warning in warnings:
150
+ if not warning.start_time or not warning.end_time:
151
+ continue
152
+
153
+ try:
154
+ start_time = datetime.fromisoformat(
155
+ warning.start_time.replace("Z", "+00:00")
156
+ )
157
+ end_time = datetime.fromisoformat(
158
+ warning.end_time.replace("Z", "+00:00")
159
+ )
160
+
161
+ # Check if timestamp falls within warning period
162
+ if start_time <= timestamp <= end_time:
163
+ applicable_warnings.append(warning)
164
+
165
+ except (ValueError, AttributeError):
166
+ # Skip warnings with invalid time formats
167
+ continue
168
+
169
+ return applicable_warnings
170
+
171
+ except (ValueError, AttributeError):
172
+ # Return empty list if timestamp parsing fails
173
+ return []
@@ -0,0 +1,324 @@
1
+ Metadata-Version: 2.4
2
+ Name: meteo_lt-pkg
3
+ Version: 0.4.0
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=3RCNUV9guscSADrcLRbsRomhp9rpPDrMVdCmKXsohmw,3006
3
+ meteo_lt/client.py,sha256=j5F1Ag00EnEI8ErLz0Q2lYqWfO8YgqYlsAHfhzkGyWE,2798
4
+ meteo_lt/const.py,sha256=1xeQ2wErqPREnYfK4OogadlVIu0p2gGoILsOcsIfp8Q,2702
5
+ meteo_lt/models.py,sha256=ki0kVe_uUVOMSC2VQsW5i2pd9A06wvuupn7jgSwyQ-0,4838
6
+ meteo_lt/utils.py,sha256=SL7ZeTEfdrdQoe_DOJaiA2zVxt08ujyFJ24JxJm1Hks,1081
7
+ meteo_lt/warnings.py,sha256=XpHMOkwqXB_mgQccyPdOlmRFcOV58Dkwtva0kNDxdpQ,6520
8
+ meteo_lt_pkg-0.4.0.dist-info/licenses/LICENSE,sha256=3IGi6xn6NUdXGvcdwD0MUbhy3Yz5NRnUjJrwKanFAD4,1073
9
+ meteo_lt_pkg-0.4.0.dist-info/METADATA,sha256=o8GQJ175gfSyajh8oYbXY2dYDwZ0KuTFJjLdY_8EMPM,10328
10
+ meteo_lt_pkg-0.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
+ meteo_lt_pkg-0.4.0.dist-info/top_level.txt,sha256=-aEdc9FzHhcIH4_0TNdKNxuvDnS3chKoJy6LK9Ud-G4,9
12
+ meteo_lt_pkg-0.4.0.dist-info/RECORD,,
@@ -1,179 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: meteo_lt-pkg
3
- Version: 0.3.0
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
- ## Usage
49
-
50
- Initializing the API Client
51
- To start using the library, you need to initialize the `MeteoLtAPI` client:
52
-
53
- ```python
54
- from meteo_lt import MeteoLtAPI
55
-
56
- api_client = MeteoLtAPI()
57
- ```
58
-
59
- ### Fetching Places
60
-
61
- To get the list of available places:
62
-
63
- ```python
64
- import asyncio
65
-
66
- async def fetch_places():
67
- await api_client.fetch_places()
68
- for place in api_client.places:
69
- print(place)
70
-
71
- asyncio.run(fetch_places())
72
- ```
73
-
74
- ### Getting the Nearest Place
75
-
76
- You can find the nearest place using latitude and longitude coordinates:
77
-
78
- ```python
79
- async def find_nearest_place(latitude, longitude):
80
- nearest_place = await api_client.get_nearest_place(latitude, longitude)
81
- print(f"Nearest place: {nearest_place.name}")
82
-
83
- # Example coordinates for Vilnius, Lithuania
84
- asyncio.run(find_nearest_place(54.6872, 25.2797))
85
- ```
86
-
87
- Also, if no places are retrieved before, that is done automatically in `get_nearest_place` method.
88
-
89
- ### Fetching Weather Forecast
90
-
91
- To get the weather forecast for a specific place, use the get_forecast method with the place code:
92
-
93
- ```python
94
- async def fetch_forecast(place_code):
95
- forecast = await api_client.get_forecast(place_code)
96
- current_conditions = forecast.current_conditions()
97
- print(f"Current temperature: {current_conditions.temperature}°C")
98
-
99
- # Example place code for Vilnius, Lithuania
100
- asyncio.run(fetch_forecast("vilnius"))
101
- ```
102
- >**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.
103
-
104
- ## Data Models
105
-
106
- The package includes several data models to represent the API responses:
107
-
108
- ### Coordinates
109
-
110
- Represents geographic coordinates.
111
-
112
- ```python
113
- from meteo_lt import Coordinates
114
-
115
- coords = Coordinates(latitude=54.6872, longitude=25.2797)
116
- print(coords)
117
- ```
118
-
119
- ### Place
120
-
121
- Represents a place with associated metadata.
122
-
123
- ```python
124
- from meteo_lt import Place
125
-
126
- place = Place(code="vilnius", name="Vilnius", administrative_division="Vilnius City Municipality", country="LT", coordinates=coords)
127
- print(place.latitude, place.longitude)
128
- ```
129
-
130
- ### ForecastTimestamp
131
-
132
- Represents a timestamp within the weather forecast, including various weather parameters.
133
-
134
- ```python
135
- from meteo_lt import ForecastTimestamp
136
-
137
- forecast_timestamp = ForecastTimestamp(
138
- datetime="2024-07-23T12:00:00+00:00",
139
- temperature=25.5,
140
- apparent_temperature=27.0,
141
- condition_code="clear",
142
- wind_speed=5.0,
143
- wind_gust_speed=8.0,
144
- wind_bearing=180,
145
- cloud_coverage=20,
146
- pressure=1012,
147
- humidity=60,
148
- precipitation=0
149
- )
150
- print(forecast_timestamp.condition)
151
- ```
152
-
153
- ### Forecast
154
-
155
- Represents the weather forecast for a place, containing multiple forecast timestamps.
156
-
157
- ```python
158
- from meteo_lt import Forecast
159
-
160
- forecast = Forecast(
161
- place=place,
162
- forecast_created=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
163
- forecast_timestamps=[forecast_timestamp]
164
- )
165
- print(forecast.current_conditions().temperature)
166
- ```
167
-
168
- ## Contributing
169
-
170
- 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).
171
-
172
- ***
173
-
174
- [commits-shield]: https://img.shields.io/github/commit-activity/y/Brunas/meteo_lt-pkg.svg?style=flat-square
175
- [commits]: https://github.com/Brunas/meteo_lt-pkg/commits/main
176
- [license-shield]: https://img.shields.io/github/license/Brunas/meteo_lt-pkg.svg?style=flat-square
177
- [maintenance-shield]: https://img.shields.io/badge/maintainer-Brunas%20%40Brunas-blue.svg?style=flat-square
178
- [releases-shield]: https://img.shields.io/github/release/Brunas/meteo_lt-pkg.svg?style=flat-square
179
- [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=1dRr1ICQBs-3W7_B_-hb0_lxcj32kR-gf3mVOWaiiuI,4042
4
- meteo_lt/utils.py,sha256=SL7ZeTEfdrdQoe_DOJaiA2zVxt08ujyFJ24JxJm1Hks,1081
5
- meteo_lt_pkg-0.3.0.dist-info/licenses/LICENSE,sha256=3IGi6xn6NUdXGvcdwD0MUbhy3Yz5NRnUjJrwKanFAD4,1073
6
- meteo_lt_pkg-0.3.0.dist-info/METADATA,sha256=QiNE4LauSE-nX7WhQQD1k5azCP9U_R0n9OCmHIURYwg,5692
7
- meteo_lt_pkg-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
- meteo_lt_pkg-0.3.0.dist-info/top_level.txt,sha256=-aEdc9FzHhcIH4_0TNdKNxuvDnS3chKoJy6LK9Ud-G4,9
9
- meteo_lt_pkg-0.3.0.dist-info/RECORD,,