meteostat 2.0.0__py3-none-any.whl → 2.0.1__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.
meteostat/__init__.py CHANGED
@@ -12,29 +12,34 @@ The code is licensed under the MIT license.
12
12
  """
13
13
 
14
14
  __appname__ = "meteostat"
15
- __version__ = "2.0.0"
15
+ __version__ = "2.0.1"
16
16
 
17
17
  from meteostat.api.daily import daily
18
18
  from meteostat.api.hourly import hourly
19
19
  from meteostat.api.interpolate import interpolate
20
+ from meteostat.api.inventory import Inventory
20
21
  from meteostat.api.merge import merge
21
22
  from meteostat.api.monthly import monthly
22
23
  from meteostat.api.normals import normals
23
24
  from meteostat.api.point import Point
24
25
  from meteostat.api.stations import stations
26
+ from meteostat.api.timeseries import TimeSeries
25
27
  from meteostat.core.cache import purge
26
- from meteostat.core.config import config
27
- from meteostat.enumerations import Parameter, Provider, UnitSystem
28
+ from meteostat.api.config import config
29
+ from meteostat.enumerations import Granularity, Parameter, Provider, UnitSystem
28
30
  from meteostat.interpolation.lapserate import lapse_rate
29
- from meteostat.typing import Station
31
+ from meteostat.typing import Station, License
30
32
 
31
33
  # Export public API
32
34
  __all__ = [
33
35
  "config",
34
36
  "daily",
35
37
  "hourly",
38
+ "Granularity",
36
39
  "interpolate",
40
+ "Inventory",
37
41
  "lapse_rate",
42
+ "License",
38
43
  "merge",
39
44
  "monthly",
40
45
  "normals",
@@ -44,5 +49,6 @@ __all__ = [
44
49
  "purge",
45
50
  "Station",
46
51
  "stations",
52
+ "TimeSeries",
47
53
  "UnitSystem",
48
54
  ]
@@ -15,9 +15,9 @@ from meteostat.enumerations import TTL, Parameter
15
15
  from meteostat.utils.types import extract_property_type, validate_parsed_value
16
16
 
17
17
 
18
- class Config:
18
+ class ConfigService:
19
19
  """
20
- Configuration Base Class
20
+ Configuration Service for Meteostat
21
21
  """
22
22
 
23
23
  prefix: str
@@ -92,9 +92,9 @@ class Config:
92
92
  self._set_env_value(key, value)
93
93
 
94
94
 
95
- class ConfigService(Config):
95
+ class Config(ConfigService):
96
96
  """
97
- Configuration Service for Meteostat
97
+ Meteostat Configuration
98
98
 
99
99
  Manages all configuration settings including cache, network, stations,
100
100
  interpolation, and provider-specific settings. Supports loading configuration
@@ -155,4 +155,4 @@ class ConfigService(Config):
155
155
  metno_user_agent: Optional[str] = None
156
156
 
157
157
 
158
- config = ConfigService("MS")
158
+ config = Config("MS")
@@ -12,12 +12,19 @@ import pandas as pd
12
12
  from meteostat.api.point import Point
13
13
  from meteostat.api.timeseries import TimeSeries
14
14
  from meteostat.typing import Station
15
+ from meteostat.enumerations import Parameter
15
16
  from meteostat.interpolation.lapserate import apply_lapse_rate
16
17
  from meteostat.interpolation.nearest import nearest_neighbor
17
18
  from meteostat.interpolation.idw import inverse_distance_weighting
18
19
  from meteostat.utils.data import aggregate_sources, reshape_by_source, stations_to_df
19
20
  from meteostat.utils.geo import get_distance
20
21
  from meteostat.utils.parsers import parse_station
22
+ from meteostat.core.schema import schema_service
23
+ from meteostat.core.logger import logger
24
+
25
+
26
+ # Parameters that are categorical and should not use IDW interpolation
27
+ CATEGORICAL_PARAMETERS = {Parameter.WDIR, Parameter.CLDC, Parameter.COCO}
21
28
 
22
29
 
23
30
  def _create_timeseries(
@@ -77,6 +84,191 @@ def _add_source_columns(
77
84
  return result
78
85
 
79
86
 
87
+ def _prepare_data_with_distances(
88
+ df: pd.DataFrame, point: Point, elevation_weight: float
89
+ ) -> pd.DataFrame:
90
+ """
91
+ Add distance and elevation calculations to the DataFrame
92
+ """
93
+ # Add distance column
94
+ df["distance"] = get_distance(
95
+ point.latitude, point.longitude, df["latitude"], df["longitude"]
96
+ )
97
+
98
+ # Add effective distance column if elevation is available
99
+ if point.elevation is not None and "elevation" in df.columns:
100
+ elev_diff = np.abs(df["elevation"] - point.elevation)
101
+ df["effective_distance"] = np.sqrt(
102
+ df["distance"] ** 2 + (elev_diff * elevation_weight) ** 2
103
+ )
104
+ else:
105
+ df["effective_distance"] = df["distance"]
106
+
107
+ # Add elevation difference column
108
+ if "elevation" in df.columns and point.elevation is not None:
109
+ df["elevation_diff"] = np.abs(df["elevation"] - point.elevation)
110
+ else:
111
+ df["elevation_diff"] = np.nan
112
+
113
+ return df
114
+
115
+
116
+ def _should_use_nearest_neighbor(
117
+ df: pd.DataFrame,
118
+ point: Point,
119
+ distance_threshold: Union[int, None],
120
+ elevation_threshold: Union[int, None],
121
+ ) -> bool:
122
+ """
123
+ Determine if nearest neighbor should be used based on thresholds
124
+ """
125
+ min_distance = df["distance"].min()
126
+ use_nearest = distance_threshold is None or min_distance <= distance_threshold
127
+
128
+ if use_nearest and point.elevation is not None and "elevation" in df.columns:
129
+ min_elev_diff = np.abs(df["elevation"] - point.elevation).min()
130
+ use_nearest = (
131
+ elevation_threshold is None or min_elev_diff <= elevation_threshold
132
+ )
133
+
134
+ return use_nearest
135
+
136
+
137
+ def _get_categorical_columns(df: pd.DataFrame) -> list:
138
+ """
139
+ Identify categorical columns in the data (excluding source columns)
140
+ """
141
+ data_cols = [c for c in df.columns if not c.endswith("_source")]
142
+ return [c for c in data_cols if c in CATEGORICAL_PARAMETERS]
143
+
144
+
145
+ def _interpolate_with_nearest_neighbor(
146
+ df: pd.DataFrame,
147
+ ts: TimeSeries,
148
+ point: Point,
149
+ distance_threshold: Union[int, None],
150
+ elevation_threshold: Union[int, None],
151
+ ) -> Optional[pd.DataFrame]:
152
+ """
153
+ Perform nearest neighbor interpolation with threshold filtering
154
+ """
155
+ distance_filter = (
156
+ pd.Series([True] * len(df), index=df.index)
157
+ if distance_threshold is None
158
+ else (df["distance"] <= distance_threshold)
159
+ )
160
+ elevation_filter = (
161
+ pd.Series([True] * len(df), index=df.index)
162
+ if elevation_threshold is None
163
+ else (np.abs(df["elevation"] - point.elevation) <= elevation_threshold)
164
+ )
165
+ df_filtered = df[distance_filter & elevation_filter]
166
+ return nearest_neighbor(df_filtered, ts, point)
167
+
168
+
169
+ def _interpolate_with_idw_and_categorical(
170
+ df: pd.DataFrame,
171
+ ts: TimeSeries,
172
+ point: Point,
173
+ categorical_cols: list,
174
+ power: float,
175
+ ) -> Optional[pd.DataFrame]:
176
+ """
177
+ Perform IDW interpolation for non-categorical parameters and nearest neighbor for categorical
178
+ """
179
+ # For categorical parameters, always use nearest neighbor
180
+ if categorical_cols:
181
+ df_categorical = nearest_neighbor(df, ts, point)
182
+ # Keep only categorical columns that exist in the result
183
+ existing_categorical = [
184
+ c for c in categorical_cols if c in df_categorical.columns
185
+ ]
186
+ df_categorical = (
187
+ df_categorical[existing_categorical]
188
+ if existing_categorical
189
+ else pd.DataFrame()
190
+ )
191
+ else:
192
+ df_categorical = pd.DataFrame()
193
+
194
+ # Perform IDW interpolation for all parameters
195
+ idw_func = inverse_distance_weighting(power=power)
196
+ df_idw = idw_func(df, ts, point)
197
+
198
+ # Remove categorical columns from IDW result if they exist
199
+ if not df_categorical.empty and df_idw is not None:
200
+ # Drop categorical columns from IDW result
201
+ idw_cols_to_keep = [c for c in df_idw.columns if c not in categorical_cols]
202
+ df_idw = df_idw[idw_cols_to_keep] if idw_cols_to_keep else pd.DataFrame()
203
+
204
+ # Combine categorical (nearest) and non-categorical (IDW) results
205
+ if not df_categorical.empty and not df_idw.empty:
206
+ return pd.concat([df_idw, df_categorical], axis=1)
207
+ elif not df_categorical.empty:
208
+ return df_categorical
209
+ else:
210
+ return df_idw
211
+
212
+
213
+ def _merge_interpolation_results(
214
+ df_nearest: Optional[pd.DataFrame],
215
+ df_idw: Optional[pd.DataFrame],
216
+ use_nearest: bool,
217
+ ) -> Optional[pd.DataFrame]:
218
+ """
219
+ Merge nearest neighbor and IDW results with appropriate priority
220
+ """
221
+ if use_nearest and df_nearest is not None and len(df_nearest) > 0:
222
+ if df_idw is not None:
223
+ # Combine nearest and IDW results, prioritizing nearest values
224
+ return df_nearest.combine_first(df_idw)
225
+ else:
226
+ return df_nearest
227
+ else:
228
+ return df_idw
229
+
230
+
231
+ def _postprocess_result(
232
+ result: pd.DataFrame, df: pd.DataFrame, ts: TimeSeries
233
+ ) -> pd.DataFrame:
234
+ """
235
+ Post-process the interpolation result: drop location columns, add sources, format, reshape
236
+ """
237
+ # Drop location-related columns
238
+ result = result.drop(
239
+ [
240
+ "latitude",
241
+ "longitude",
242
+ "elevation",
243
+ "distance",
244
+ "effective_distance",
245
+ "elevation_diff",
246
+ ],
247
+ axis=1,
248
+ errors="ignore",
249
+ )
250
+
251
+ # Add source columns
252
+ result = _add_source_columns(result, df)
253
+
254
+ # Reshape by source
255
+ result = reshape_by_source(result)
256
+
257
+ # Add station index
258
+ result["station"] = "$0001"
259
+ result = result.set_index("station", append=True).reorder_levels(
260
+ ["station", "time", "source"]
261
+ )
262
+
263
+ # Reorder columns to match the canonical schema order
264
+ result = schema_service.purge(result, ts.parameters)
265
+
266
+ # Format the result using schema_service to apply proper rounding
267
+ result = schema_service.format(result, ts.granularity)
268
+
269
+ return result
270
+
271
+
80
272
  def interpolate(
81
273
  ts: TimeSeries,
82
274
  point: Point,
@@ -127,27 +319,11 @@ def interpolate(
127
319
 
128
320
  # If no data is returned, return None
129
321
  if df is None:
322
+ logger.debug("No data available for interpolation. Returning empty TimeSeries.")
130
323
  return _create_timeseries(ts, point)
131
324
 
132
- # Add distance column
133
- df["distance"] = get_distance(
134
- point.latitude, point.longitude, df["latitude"], df["longitude"]
135
- )
136
-
137
- # Add effective distance column if elevation is available
138
- if point.elevation is not None and "elevation" in df.columns:
139
- elev_diff = np.abs(df["elevation"] - point.elevation)
140
- df["effective_distance"] = np.sqrt(
141
- df["distance"] ** 2 + (elev_diff * elevation_weight) ** 2
142
- )
143
- else:
144
- df["effective_distance"] = df["distance"]
145
-
146
- # Add elevation difference column
147
- if "elevation" in df.columns and point.elevation is not None:
148
- df["elevation_diff"] = np.abs(df["elevation"] - point.elevation)
149
- else:
150
- df["elevation_diff"] = np.nan
325
+ # Prepare data with distance and elevation calculations
326
+ df = _prepare_data_with_distances(df, point, elevation_weight)
151
327
 
152
328
  # Apply lapse rate if specified and elevation is available
153
329
  if (
@@ -155,86 +331,48 @@ def interpolate(
155
331
  and point.elevation
156
332
  and df["elevation_diff"].max() >= lapse_rate_threshold
157
333
  ):
334
+ logger.debug("Applying lapse rate correction.")
158
335
  df = apply_lapse_rate(df, point.elevation, lapse_rate)
159
336
 
160
- # Check if any stations are close enough for nearest neighbor
161
- min_distance = df["distance"].min()
162
- use_nearest = distance_threshold is None or min_distance <= distance_threshold
163
- if use_nearest and point.elevation is not None and "elevation" in df.columns:
164
- # Calculate minimum elevation difference
165
- min_elev_diff = np.abs(df["elevation"] - point.elevation).min()
166
- use_nearest = (
167
- elevation_threshold is None or min_elev_diff <= elevation_threshold
168
- )
337
+ # Determine if nearest neighbor should be used
338
+ use_nearest = _should_use_nearest_neighbor(
339
+ df, point, distance_threshold, elevation_threshold
340
+ )
169
341
 
170
- # Initialize variables
342
+ # Identify categorical columns
343
+ categorical_cols = _get_categorical_columns(df)
344
+ logger.debug(f"Categorical columns identified: {categorical_cols}")
345
+
346
+ # Perform interpolation
171
347
  df_nearest = None
172
348
  df_idw = None
173
349
 
174
- # Perform nearest neighbor if applicable
175
350
  if use_nearest:
176
- # Filter applicable stations based on thresholds
177
- distance_filter = (
178
- pd.Series([True] * len(df), index=df.index)
179
- if distance_threshold is None
180
- else (df["distance"] <= distance_threshold)
181
- )
182
- elevation_filter = (
183
- pd.Series([True] * len(df), index=df.index)
184
- if elevation_threshold is None
185
- else (np.abs(df["elevation"] - point.elevation) <= elevation_threshold)
351
+ logger.debug("Using nearest neighbor interpolation.")
352
+ df_nearest = _interpolate_with_nearest_neighbor(
353
+ df, ts, point, distance_threshold, elevation_threshold
186
354
  )
187
- df_filtered = df[distance_filter & elevation_filter]
188
- df_nearest = nearest_neighbor(df_filtered, ts, point)
189
355
 
190
- # Check if we need to use IDW
356
+ # Use IDW if nearest neighbor doesn't provide complete data
191
357
  if (
192
358
  not use_nearest
193
359
  or df_nearest is None
194
360
  or len(df_nearest) == 0
195
361
  or df_nearest.isna().any().any()
196
362
  ):
197
- # Perform IDW interpolation
198
- idw_func = inverse_distance_weighting(power=power)
199
- df_idw = idw_func(df, ts, point)
363
+ logger.debug("Using IDW interpolation.")
364
+ df_idw = _interpolate_with_idw_and_categorical(
365
+ df, ts, point, categorical_cols, power
366
+ )
200
367
 
201
- # Merge DataFrames with priority to nearest neighbor
202
- if use_nearest and df_nearest is not None and len(df_nearest) > 0:
203
- if df_idw is not None:
204
- # Combine nearest and IDW results, prioritizing nearest values
205
- result = df_nearest.combine_first(df_idw)
206
- else:
207
- result = df_nearest
208
- else:
209
- result = df_idw
368
+ # Merge results
369
+ result = _merge_interpolation_results(df_nearest, df_idw, use_nearest)
210
370
 
211
371
  # If no data is returned, return None
212
372
  if result is None or result.empty:
213
373
  return _create_timeseries(ts, point)
214
374
 
215
- # Drop location-related columns & return
216
- result = result.drop(
217
- [
218
- "latitude",
219
- "longitude",
220
- "elevation",
221
- "distance",
222
- "effective_distance",
223
- "elevation_diff",
224
- ],
225
- axis=1,
226
- )
227
-
228
- # Add source columns: aggregate all columns that end with "_source"
229
- result = _add_source_columns(result, df)
230
-
231
- # Reshape by source
232
- result = reshape_by_source(result)
233
-
234
- # Add station index
235
- result["station"] = "$0001"
236
- result = result.set_index("station", append=True).reorder_levels(
237
- ["station", "time", "source"]
238
- )
375
+ # Post-process result
376
+ result = _postprocess_result(result, df, ts)
239
377
 
240
378
  return _create_timeseries(ts, point, result)
meteostat/api/stations.py CHANGED
@@ -15,7 +15,7 @@ from requests import Response
15
15
  from meteostat.api.inventory import Inventory
16
16
  from meteostat.api.point import Point
17
17
  from meteostat.core.cache import cache_service
18
- from meteostat.core.config import config
18
+ from meteostat.api.config import config
19
19
  from meteostat.core.logger import logger
20
20
  from meteostat.core.network import network_service
21
21
  from meteostat.enumerations import Provider
meteostat/core/cache.py CHANGED
@@ -14,7 +14,7 @@ from typing import Any, Callable, Optional
14
14
 
15
15
  import pandas as pd
16
16
 
17
- from meteostat.core.config import config
17
+ from meteostat.api.config import config
18
18
  from meteostat.core.logger import logger
19
19
 
20
20
 
meteostat/core/data.py CHANGED
@@ -18,6 +18,7 @@ from meteostat.core.schema import schema_service
18
18
  from meteostat.enumerations import Parameter, Provider
19
19
  from meteostat.typing import Station, Request
20
20
  from meteostat.utils.data import stations_to_df
21
+ from meteostat.utils.guards import request_size_guard
21
22
 
22
23
 
23
24
  class DataService:
@@ -147,6 +148,9 @@ class DataService:
147
148
  """
148
149
  Load meteorological time series data from different providers
149
150
  """
151
+ # Guard request
152
+ request_size_guard(req)
153
+
150
154
  # Convert stations to list if single Station
151
155
  stations: List[Station] = (
152
156
  cast(List[Station], req.station)
meteostat/core/network.py CHANGED
@@ -11,7 +11,7 @@ import requests
11
11
 
12
12
  from meteostat import __version__
13
13
  from meteostat.core.logger import logger
14
- from meteostat.core.config import config
14
+ from meteostat.api.config import config
15
15
 
16
16
 
17
17
  class NetworkService:
@@ -5,7 +5,7 @@ import numpy as np
5
5
  import pandas as pd
6
6
 
7
7
  from meteostat.api.timeseries import TimeSeries
8
- from meteostat.core.config import config
8
+ from meteostat.api.config import config
9
9
  from meteostat.enumerations import Parameter
10
10
 
11
11
 
@@ -10,7 +10,7 @@ from typing import List, Optional
10
10
  import pandas as pd
11
11
 
12
12
  from meteostat.core.logger import logger
13
- from meteostat.core.config import config
13
+ from meteostat.api.config import config
14
14
  from meteostat.enumerations import TTL, Parameter
15
15
  from meteostat.typing import ProviderRequest, Station
16
16
  from meteostat.core.cache import cache_service
@@ -14,7 +14,7 @@ from zipfile import ZipFile
14
14
 
15
15
  import pandas as pd
16
16
 
17
- from meteostat.core.config import config
17
+ from meteostat.api.config import config
18
18
  from meteostat.enumerations import TTL, Parameter
19
19
  from meteostat.typing import ProviderRequest
20
20
  from meteostat.core.cache import cache_service
@@ -18,7 +18,7 @@ from meteostat.enumerations import TTL, Parameter
18
18
  from meteostat.core.logger import logger
19
19
  from meteostat.typing import ProviderRequest, Station
20
20
  from meteostat.core.cache import cache_service
21
- from meteostat.core.config import config
21
+ from meteostat.api.config import config
22
22
  from meteostat.utils.conversions import ms_to_kmh
23
23
  from meteostat.providers.dwd.shared import get_condicode
24
24
  from meteostat.providers.dwd.shared import get_ftp_connection
@@ -1,7 +1,7 @@
1
1
  from typing import Union
2
2
  from ftplib import FTP
3
3
 
4
- from meteostat.core.config import config
4
+ from meteostat.api.config import config
5
5
 
6
6
 
7
7
  DWD_FTP_SERVER = config.dwd_ftp_host
@@ -10,7 +10,7 @@ import pandas as pd
10
10
  from meteostat.providers.meteostat.shared import filter_model_data, handle_exceptions
11
11
  from meteostat.typing import ProviderRequest
12
12
  from meteostat.core.cache import cache_service
13
- from meteostat.core.config import config
13
+ from meteostat.api.config import config
14
14
  from meteostat.utils.data import reshape_by_source
15
15
 
16
16
  ENDPOINT = config.daily_endpoint
@@ -9,7 +9,7 @@ import pandas as pd
9
9
 
10
10
  from meteostat.providers.meteostat.shared import filter_model_data, handle_exceptions
11
11
  from meteostat.typing import ProviderRequest
12
- from meteostat.core.config import config
12
+ from meteostat.api.config import config
13
13
  from meteostat.core.cache import cache_service
14
14
  from meteostat.utils.data import reshape_by_source
15
15
 
@@ -6,7 +6,7 @@ from typing import Optional
6
6
 
7
7
  import pandas as pd
8
8
 
9
- from meteostat.core.config import config
9
+ from meteostat.api.config import config
10
10
  from meteostat.enumerations import TTL
11
11
  from meteostat.providers.meteostat.shared import filter_model_data, handle_exceptions
12
12
  from meteostat.typing import ProviderRequest
@@ -4,7 +4,7 @@ from typing import Optional, Callable, TypeVar
4
4
 
5
5
  import pandas as pd
6
6
 
7
- from meteostat.core.config import config
7
+ from meteostat.api.config import config
8
8
  from meteostat.core.logger import logger
9
9
  from meteostat.core.providers import provider_service
10
10
  from meteostat.enumerations import Grade
@@ -3,7 +3,7 @@ from urllib.error import HTTPError
3
3
 
4
4
  import pandas as pd
5
5
 
6
- from meteostat.core.config import config
6
+ from meteostat.api.config import config
7
7
  from meteostat.enumerations import TTL, Parameter
8
8
  from meteostat.core.logger import logger
9
9
  from meteostat.core.network import network_service
@@ -8,7 +8,7 @@ import pandas as pd
8
8
  from metar import Metar
9
9
 
10
10
  from meteostat.core.logger import logger
11
- from meteostat.core.config import config
11
+ from meteostat.api.config import config
12
12
  from meteostat.enumerations import TTL, Frequency, Parameter
13
13
  from meteostat.typing import ProviderRequest
14
14
  from meteostat.utils.conversions import temp_dwpt_to_rhum
@@ -0,0 +1,51 @@
1
+ """
2
+ Guard functions for Meteostat.
3
+
4
+ The code is licensed under the MIT license.
5
+ """
6
+
7
+ from datetime import datetime
8
+ from meteostat.api.config import config
9
+ from meteostat.core.logger import logger
10
+ from meteostat.enumerations import Granularity
11
+ from meteostat.typing import Request
12
+
13
+
14
+ def request_size_guard(req: Request) -> None:
15
+ """
16
+ Guard to block large requests
17
+ """
18
+ if not config.block_large_requests:
19
+ logger.debug("Large request blocking is disabled.")
20
+ return
21
+
22
+ if isinstance(req.station, list) and len(req.station) > 10:
23
+ raise ValueError(
24
+ "Requests with more than 10 stations are blocked by default. "
25
+ "To enable large requests, set `config.block_large_requests = False`."
26
+ )
27
+
28
+ if req.granularity not in [Granularity.HOURLY, Granularity.DAILY]:
29
+ return
30
+
31
+ if req.start is None:
32
+ raise ValueError(
33
+ "Hourly and daily requests without a start date are blocked by default. "
34
+ "To enable large requests, set `config.block_large_requests = False`."
35
+ )
36
+
37
+ time_diff_years = abs((req.end or datetime.now()).year - req.start.year)
38
+
39
+ logger.debug(f"Request time range: {time_diff_years} years.")
40
+
41
+ if req.granularity is Granularity.HOURLY and time_diff_years > 3:
42
+ raise ValueError(
43
+ "Hourly requests longer than 3 years are blocked by default. "
44
+ "To enable large requests, set `config.block_large_requests = False`."
45
+ )
46
+
47
+ if req.granularity is Granularity.DAILY and time_diff_years > 30:
48
+ raise ValueError(
49
+ "Daily requests longer than 30 years are blocked by default. "
50
+ "To enable large requests, set `config.block_large_requests = False`."
51
+ )
@@ -13,7 +13,6 @@ import pytz
13
13
 
14
14
  from meteostat.api.stations import stations as stations_service
15
15
  from meteostat.api.point import Point
16
- from meteostat.core.config import config
17
16
  from meteostat.typing import Station
18
17
 
19
18
 
@@ -54,12 +53,6 @@ def parse_station(
54
53
  # It's a list
55
54
  stations = station
56
55
 
57
- if config.block_large_requests and len(stations) > 10:
58
- raise ValueError(
59
- "Requests with more than 10 stations are blocked by default. "
60
- "To enable large requests, set `config.block_large_requests = False`."
61
- )
62
-
63
56
  # Get station meta data
64
57
  data = []
65
58
  point_counter = 0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meteostat
3
- Version: 2.0.0
3
+ Version: 2.0.1
4
4
  Summary: Access and analyze historical weather and climate data with Python.
5
5
  License-File: LICENSE
6
6
  Author: Meteostat
@@ -18,12 +18,11 @@ Description-Content-Type: text/markdown
18
18
  <!-- PROJECT SHIELDS -->
19
19
  <div align="center">
20
20
 
21
- [![Contributors][contributors-shield]][contributors-url]
22
- [![Forks][forks-shield]][forks-url]
23
- [![Stargazers][stars-shield]][stars-url]
21
+ [![Downloads][downloads-shield]][downloads-url]
22
+ [![Provider Tests][provider-tests-shield]][provider-tests-url]
24
23
  [![Issues][issues-shield]][issues-url]
25
- [![Unlicense License][license-shield]][license-url]
26
- [![LinkedIn][linkedin-shield]][linkedin-url]
24
+ [![MIT License][license-shield]][license-url]
25
+ [![Stargazers][stars-shield]][stars-url]
27
26
 
28
27
  </div>
29
28
 
@@ -118,17 +117,14 @@ Meteostat is licensed under the [**MIT License**](https://github.com/meteostat/m
118
117
 
119
118
  <!-- MARKDOWN LINKS & IMAGES -->
120
119
  <!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->
121
- [contributors-shield]: https://img.shields.io/github/contributors/meteostat/meteostat.svg?style=for-the-badge
122
- [contributors-url]: https://github.com/meteostat/meteostat/graphs/contributors
123
- [forks-shield]: https://img.shields.io/github/forks/meteostat/meteostat.svg?style=for-the-badge
124
- [forks-url]: https://github.com/meteostat/meteostat/network/members
125
- [stars-shield]: https://img.shields.io/github/stars/meteostat/meteostat.svg?style=for-the-badge
126
- [stars-url]: https://github.com/meteostat/meteostat/stargazers
127
- [issues-shield]: https://img.shields.io/github/issues/meteostat/meteostat.svg?style=for-the-badge
120
+ [downloads-shield]: https://img.shields.io/pypi/dm/meteostat
121
+ [downloads-url]: https://pypi.org/project/meteostat/
122
+ [provider-tests-shield]: https://github.com/meteostat/meteostat/actions/workflows/provider-tests.yml/badge.svg
123
+ [provider-tests-url]: https://github.com/meteostat/meteostat/actions/workflows/provider-tests.yml
124
+ [issues-shield]: https://img.shields.io/github/issues/meteostat/meteostat.svg
128
125
  [issues-url]: https://github.com/meteostat/meteostat/issues
129
- [license-shield]: https://img.shields.io/github/license/meteostat/meteostat.svg?style=for-the-badge
130
- [license-url]: https://github.com/meteostat/meteostat/blob/main/LICENSE
131
- [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555
132
- [linkedin-url]: https://www.linkedin.com/company/meteostat
126
+ [license-shield]: https://img.shields.io/github/license/meteostat/meteostat.svg
127
+ [license-url]: https://github.com/meteostat/meteostat?tab=readme-ov-file#-license
133
128
  [product-screenshot]: https://dev.meteostat.net/assets/images/example-8b6cf2a3fe2efa285bc72d7dc72c4865.png
134
-
129
+ [stars-shield]: https://img.shields.io/github/stars/meteostat/meteostat.svg
130
+ [stars-url]: https://github.com/meteostat/meteostat/stargazers
@@ -1,20 +1,20 @@
1
- meteostat/__init__.py,sha256=-Oq1cCBUSBIUMYK01RLV4oXgfo0f-F6S_9iVUNh39m0,1371
1
+ meteostat/__init__.py,sha256=uoBFnarB7WQtLj61mM-U2FHBq06FLHKLfdSKlTVq7i4,1555
2
2
  meteostat/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ meteostat/api/config.py,sha256=DpZ20TLcc7S1uJV_N_SBv2qtVorNgB0hFzX2sHs0tXM,5065
3
4
  meteostat/api/daily.py,sha256=SUYxR7HC5Cqyd6cBG42QmSpEP74HwDq5VnqfSznM46Y,2371
4
5
  meteostat/api/hourly.py,sha256=KGJ4xBwO__fBrQ5ll5ruLEkrpOCD8bybSBcXJ94-ntE,2556
5
- meteostat/api/interpolate.py,sha256=UJH_TJqCsmOTT3-DP2hh9rn3mHiISqqxHE083TtFb-M,8280
6
+ meteostat/api/interpolate.py,sha256=6i128g_x5ea3X2Nkn6IlrtPaKqRfakozOcSheTbmxIM,12602
6
7
  meteostat/api/inventory.py,sha256=xida9SVdYPm4GpiloVg-zXvkeUi7Egaj_XbxWWhNHuI,1405
7
8
  meteostat/api/merge.py,sha256=CtoItPFsRxXWu50g1zjC9q94wTDC6ZzCLhvU3jmZhX4,2638
8
9
  meteostat/api/monthly.py,sha256=cZmJiwVNxw5nNNmrx0ohy9bIN_dSt1Nk8crxcCJsJvU,2327
9
10
  meteostat/api/normals.py,sha256=3CbRCYrp_mATZwkWbgBphcmLvhoR_hSaDCj0hfsIlKk,5138
10
11
  meteostat/api/point.py,sha256=T4fRcf_ozE3O_ywS8lneSfG_dWexk-cNmVHLJKB_idc,718
11
- meteostat/api/stations.py,sha256=AuG13Rn_WMujxmnifHbNPVPDMxXzWvqDS0ZfmvRWqSE,7002
12
+ meteostat/api/stations.py,sha256=RuS4yhxq-YJ_IOLhXGv-Y3h__8SlkmmtU1BnGUUe9jI,7001
12
13
  meteostat/api/timeseries.py,sha256=zwrIT6URl4R4JYEFUDorn5QZg3VRMeKR2XMOF9D9REc,9889
13
- meteostat/core/cache.py,sha256=t1BUJXIMvQ3a1h-_dzFWiSz6lLKGwp91zVbGE97ObzY,6646
14
- meteostat/core/config.py,sha256=qjOOzh6c_yw0KzLnreWodewHwC2lTGyFm1zJVgJnKg4,5066
15
- meteostat/core/data.py,sha256=0lzp2nX4VaMj6l7EAPNR74lDd9W_8qy526P34hVsd9A,5643
14
+ meteostat/core/cache.py,sha256=Xo6KyExsCw9cRI_6-yJxJIJ9TfQ7x0IMMKarpZb2598,6645
15
+ meteostat/core/data.py,sha256=wnoqgRXeWNVCAcI_SDH-shLGpx_4z5wE6a5ayqbpZnU,5754
16
16
  meteostat/core/logger.py,sha256=MUqPTxpw6C9zky4lCeOac9rLjjYkaiHVGVVvLuxKgEg,146
17
- meteostat/core/network.py,sha256=7972WWNp90RNbCKntWCzGH-0sBFLcdExdqK6k20YzzE,1940
17
+ meteostat/core/network.py,sha256=JGjjWEpJEYJR6Q3NDbOOYdLo9laiValAip5jrxEnzKA,1939
18
18
  meteostat/core/parameters.py,sha256=7BJIkFQaxcBRi1yIDgpAYm2VZxBfVPbi5_UdZL9teXE,3288
19
19
  meteostat/core/providers.py,sha256=DChq-vILb9uWEcsZmUcUnyeuVGtl3Ote49QhibdeNBo,5490
20
20
  meteostat/core/schema.py,sha256=Jbp84bTgWmfhPYhORluGWLEnogevsFiAjwC21Y4Mbwg,5070
@@ -22,42 +22,43 @@ meteostat/core/validator.py,sha256=pJOJIU8FtYO4k7ihGqFxM6CSs9O6oO-fByYyJeJ9pZY,8
22
22
  meteostat/enumerations.py,sha256=1lWrnzejtV1uP5CjyeyN-UcYt8wBIqlc5sxwzwh5Rqg,3271
23
23
  meteostat/interpolation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  meteostat/interpolation/idw.py,sha256=AyZWxXjq8mS9BEQtxgSEHQzn7TZYIdkc22kPkF5qZww,4435
25
- meteostat/interpolation/lapserate.py,sha256=5RanfTDc6OiyuYubjjbQQo23iS05pEf0TmAnqyshoR8,2638
25
+ meteostat/interpolation/lapserate.py,sha256=D5Y2DLMgjCNmqfdm2J5MU67w0O6L0vx_TbgMaGrMQuc,2637
26
26
  meteostat/interpolation/nearest.py,sha256=xZg_6OCvU8uI-i7UbPQHjsJmtlz_DbZqrDz00dJthEM,782
27
27
  meteostat/parameters.py,sha256=w7G3ktLVasAbaR9OnjuMAvrq_c1zLz5PVt3zofDFun4,10074
28
28
  meteostat/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
- meteostat/providers/dwd/climat.py,sha256=seBHREXkALb_ACP3bMErfBnFrt_oANX_qoaDCFjPsZ8,4876
30
- meteostat/providers/dwd/daily.py,sha256=xYGmOY-jETqxpXiXHPvM2GeQuaQr3M2qn0ui4cqpscw,4031
31
- meteostat/providers/dwd/hourly.py,sha256=UukcRv3eN9jIewvACC3dHDXrRn50pfEB51YQ2qUKxjI,6250
29
+ meteostat/providers/dwd/climat.py,sha256=jEHvg9m8L0161n1j7DHOiBWyky7WL0SD-TrlkljoYCI,4875
30
+ meteostat/providers/dwd/daily.py,sha256=qVZjMUu6a1GODaWrKpzbOYdXS9HyEkKp9XwnY47Md44,4030
31
+ meteostat/providers/dwd/hourly.py,sha256=LAjnQJrp80wOhX24JwuceAtCi104DC8EDwEZetHDrso,6249
32
32
  meteostat/providers/dwd/monthly.py,sha256=JaXklCJrH_xlQPm1G6PXoVvV81qwnhT2o1mXXEzM2LQ,3658
33
33
  meteostat/providers/dwd/mosmix.py,sha256=uDalWL-_Vayyq3iiiCUWPFmKXSYqQttXiDOF2Lk_qB4,8892
34
34
  meteostat/providers/dwd/poi.py,sha256=Qv4eDsi_a8hdBQKl1LPQJ0BO2UbTxfba9gH5Ht2giZ0,3087
35
- meteostat/providers/dwd/shared.py,sha256=SxrrJFei-9W671cIVDNx7U58nmJF-DPPe3TvmmYcayc,2918
35
+ meteostat/providers/dwd/shared.py,sha256=z4LT0fd7LFUqpVPJRJyNWJxK_6TRzJFDvolv6_Y9PME,2917
36
36
  meteostat/providers/eccc/daily.py,sha256=K-akr9JXYMnsdG1YlM9SPXUcDfVzv61KsCqD9Ieo6YM,2722
37
37
  meteostat/providers/eccc/hourly.py,sha256=W2qMX0HxffMs1IlBzKY4b0M58wPJvtDw7Py4xyDuNO8,3209
38
38
  meteostat/providers/eccc/monthly.py,sha256=eA5JRpDa7-2H-Ce4FOstY24ECWj_gTY5vRbvZa_LCLE,1805
39
39
  meteostat/providers/eccc/shared.py,sha256=2m00uWYdWQrVqvxwMioLIKFxlrjLmCfqIYwjx30r_9k,1324
40
40
  meteostat/providers/index.py,sha256=6UrzxtynwbILOxg_lmE_Ylh747GZzDW_6ytGIEQHTNc,12670
41
- meteostat/providers/meteostat/daily.py,sha256=WVghOSm-yOstq7mI_e0BnMBQS8c_aCN9lR591AX8Aw0,1796
41
+ meteostat/providers/meteostat/daily.py,sha256=32vh0J5caMT9gshfUBQXoH8CB3r3qf4BUlB3VztvPN0,1795
42
42
  meteostat/providers/meteostat/daily_derived.py,sha256=_fiLSKxkaH9Caxp-Tg77rH39UXwy7SPDlCS_M-7kIqE,3170
43
- meteostat/providers/meteostat/hourly.py,sha256=pK3dEVPuv6sTGTapyu7iY-nbEbrpis_r-1e5vBYVzqQ,1799
44
- meteostat/providers/meteostat/monthly.py,sha256=-8X_VHY8Ei0YK6Vg8vnKs9tEUhxITL7PY3dzoaQyZmk,1254
43
+ meteostat/providers/meteostat/hourly.py,sha256=INO5JVaDbd3zIEo3_spIwACoBpYqDXinyrL-pW1AIAk,1798
44
+ meteostat/providers/meteostat/monthly.py,sha256=4N2ThwBroQVz2wI5UI2WpvIUGOH61gS63rP8ZVaMqOE,1253
45
45
  meteostat/providers/meteostat/monthly_derived.py,sha256=8BNSd2-xqABdr3lDU_9noypF91qhRJPhz5liTK0eoR8,3075
46
- meteostat/providers/meteostat/shared.py,sha256=3B1EyCWd1EmyNsmddm1CEoKb3QsVJEX9pwQ6SlSr3uw,2572
47
- meteostat/providers/metno/forecast.py,sha256=NBtQlcY_DbwAFEpiI2SulkEHsbTbXcOcLB9zGv4KXGc,5662
46
+ meteostat/providers/meteostat/shared.py,sha256=mK2K3tbgtCcKLMITR5NJVuJSBkPm_jLcN0dBRT9hM90,2571
47
+ meteostat/providers/metno/forecast.py,sha256=x-q21HiGkJ4yiv7vKcPnVSFin3rFGHWAZqtzJE0j4Tk,5661
48
48
  meteostat/providers/noaa/ghcnd.py,sha256=AaR-efQkbgc3afwcvMd_H25_l1dV5CFmautn9TWQ6Cw,6789
49
49
  meteostat/providers/noaa/isd_lite.py,sha256=YfqhWJjNjxirbqeGaRsQo07koEREvY-APVETkZ3eBKM,3817
50
- meteostat/providers/noaa/metar.py,sha256=b9kGnWRKMeKcR3WZR3Mgdx6v0qA3Ac8vbtrOSCRz5KM,4189
50
+ meteostat/providers/noaa/metar.py,sha256=wqH0zEUZO0gS0inK5vvxKulz-LF3NEZIAsYHmR0Do64,4188
51
51
  meteostat/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
52
  meteostat/typing.py,sha256=Q4_le4P_yOVyv1FAxtccNDfFc0OPzYKm3p-GTTptGAY,3504
53
53
  meteostat/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
54
  meteostat/utils/conversions.py,sha256=B1qaL3_OSbWniIsFCwDPnwWtmglRiBBswFK1q0NH4bI,4755
55
55
  meteostat/utils/data.py,sha256=d_aQy53jdhDAvFvo0qVPyhR2NH-WKIAeNm_kIobJOq8,6119
56
56
  meteostat/utils/geo.py,sha256=n_imXze6-u0vzFdYrgPyStKqGNuERcUmXUA-PsAja2w,664
57
- meteostat/utils/parsers.py,sha256=IDuffIdiTmOitarmv6VEYPVrhzT9GT6Gh57cayS_FsM,4941
57
+ meteostat/utils/guards.py,sha256=fuBd4_6BXfZT_jPYU6GzXgDBnCc_GVLDpI6PSK3PCno,1746
58
+ meteostat/utils/parsers.py,sha256=2y-QSubyrh_xdXJBdgWNiWNI_DDXCGxddcjGOZB9hCU,4646
58
59
  meteostat/utils/types.py,sha256=_hzGcVueIGZdjCB6mR4vVFedrPUWw0iMRaLkpnqaN2Y,3470
59
60
  meteostat/utils/validators.py,sha256=iBywF68ZhL3eD6fgemY2Tng3a-409eFU2oNZVqpYCpY,524
60
- meteostat-2.0.0.dist-info/METADATA,sha256=bCqZ8ZhjVU8ygMcDemSDOwxL4jxQJ7E01l8YSacMDQY,5312
61
- meteostat-2.0.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
62
- meteostat-2.0.0.dist-info/licenses/LICENSE,sha256=kqpl7FVzWOCe11BZqJBZ1aRQi-aK87j3ljtG7P3VxLc,1066
63
- meteostat-2.0.0.dist-info/RECORD,,
61
+ meteostat-2.0.1.dist-info/METADATA,sha256=cAXHfPnAXC-3gEpeUMfmRqURQWjOufDsXHfX_fjm650,5024
62
+ meteostat-2.0.1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
63
+ meteostat-2.0.1.dist-info/licenses/LICENSE,sha256=kqpl7FVzWOCe11BZqJBZ1aRQi-aK87j3ljtG7P3VxLc,1066
64
+ meteostat-2.0.1.dist-info/RECORD,,