meteostat 1.6.8__tar.gz → 1.7.1__tar.gz

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.
Files changed (46) hide show
  1. {meteostat-1.6.8 → meteostat-1.7.1}/PKG-INFO +3 -3
  2. {meteostat-1.6.8 → meteostat-1.7.1}/README.md +1 -1
  3. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/__init__.py +12 -1
  4. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/core/cache.py +0 -2
  5. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/core/loader.py +26 -28
  6. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/core/warn.py +1 -1
  7. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/interface/base.py +10 -7
  8. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/interface/daily.py +44 -31
  9. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/interface/hourly.py +44 -43
  10. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/interface/meteodata.py +54 -69
  11. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/interface/monthly.py +24 -19
  12. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/interface/normals.py +59 -19
  13. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/interface/point.py +1 -4
  14. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/interface/stations.py +9 -8
  15. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/interface/timeseries.py +97 -65
  16. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/series/aggregate.py +0 -1
  17. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/series/convert.py +1 -1
  18. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/series/interpolate.py +12 -2
  19. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/series/normalize.py +4 -5
  20. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/utilities/endpoint.py +1 -1
  21. meteostat-1.7.1/meteostat/utilities/helpers.py +70 -0
  22. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/utilities/mutations.py +10 -0
  23. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat.egg-info/PKG-INFO +3 -3
  24. meteostat-1.7.1/meteostat.egg-info/requires.txt +3 -0
  25. {meteostat-1.6.8 → meteostat-1.7.1}/setup.py +3 -3
  26. meteostat-1.6.8/meteostat/utilities/helpers.py +0 -32
  27. meteostat-1.6.8/meteostat.egg-info/requires.txt +0 -3
  28. {meteostat-1.6.8 → meteostat-1.7.1}/LICENSE +0 -0
  29. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/core/__init__.py +0 -0
  30. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/enumerations/__init__.py +0 -0
  31. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/enumerations/granularity.py +0 -0
  32. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/interface/__init__.py +0 -0
  33. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/interface/interpolate.py +0 -0
  34. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/series/__init__.py +0 -0
  35. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/series/count.py +0 -0
  36. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/series/coverage.py +0 -0
  37. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/series/fetch.py +0 -0
  38. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/series/stations.py +0 -0
  39. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/units.py +0 -0
  40. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/utilities/__init__.py +0 -0
  41. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/utilities/aggregations.py +0 -0
  42. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat/utilities/validations.py +0 -0
  43. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat.egg-info/SOURCES.txt +0 -0
  44. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat.egg-info/dependency_links.txt +0 -0
  45. {meteostat-1.6.8 → meteostat-1.7.1}/meteostat.egg-info/top_level.txt +0 -0
  46. {meteostat-1.6.8 → meteostat-1.7.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: meteostat
3
- Version: 1.6.8
3
+ Version: 1.7.1
4
4
  Summary: Access and analyze historical weather and climate data with Python.
5
5
  Home-page: https://github.com/meteostat/meteostat-python
6
6
  Author: Meteostat
@@ -14,7 +14,7 @@ Classifier: Topic :: Database
14
14
  Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
15
15
  Classifier: Topic :: Scientific/Engineering :: Information Analysis
16
16
  Classifier: Topic :: Scientific/Engineering :: Visualization
17
- Requires-Python: >=3.6.0
17
+ Requires-Python: >=3.8.0
18
18
  Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
20
 
@@ -22,7 +22,7 @@ License-File: LICENSE
22
22
 
23
23
  The Meteostat Python library provides a simple API for accessing open weather and climate data. The historical observations and statistics are collected by [Meteostat](https://meteostat.net) from different public interfaces, most of which are governmental.
24
24
 
25
- Among the data sources are national weather services like the National Oceanic and Atmospheric Administration (NOAA) and Germany's national meteorological service (DWD).
25
+ Among the data sources are national weather services like the National Oceanic and Atmospheric Administration (NOAA) and Germany's national weather service (DWD).
26
26
 
27
27
  Are you looking for a **hosted solution**? Try our [JSON API](https://rapidapi.com/meteostat/api/meteostat/).
28
28
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  The Meteostat Python library provides a simple API for accessing open weather and climate data. The historical observations and statistics are collected by [Meteostat](https://meteostat.net) from different public interfaces, most of which are governmental.
4
4
 
5
- Among the data sources are national weather services like the National Oceanic and Atmospheric Administration (NOAA) and Germany's national meteorological service (DWD).
5
+ Among the data sources are national weather services like the National Oceanic and Atmospheric Administration (NOAA) and Germany's national weather service (DWD).
6
6
 
7
7
  Are you looking for a **hosted solution**? Try our [JSON API](https://rapidapi.com/meteostat/api/meteostat/).
8
8
 
@@ -12,7 +12,7 @@ The code is licensed under the MIT license.
12
12
  """
13
13
 
14
14
  __appname__ = "meteostat"
15
- __version__ = "1.6.8"
15
+ __version__ = "1.7.1"
16
16
 
17
17
  from .interface.base import Base
18
18
  from .interface.timeseries import TimeSeries
@@ -22,3 +22,14 @@ from .interface.hourly import Hourly
22
22
  from .interface.daily import Daily
23
23
  from .interface.monthly import Monthly
24
24
  from .interface.normals import Normals
25
+
26
+ __all__ = [
27
+ "Base",
28
+ "TimeSeries",
29
+ "Stations",
30
+ "Point",
31
+ "Hourly",
32
+ "Daily",
33
+ "Monthly",
34
+ "Normals",
35
+ ]
@@ -53,7 +53,6 @@ def clear_cache(cls, max_age: int = None) -> None:
53
53
  """
54
54
 
55
55
  if os.path.exists(cls.cache_dir + os.sep + cls.cache_subdir):
56
-
57
56
  # Set max_age
58
57
  if max_age is None:
59
58
  max_age = cls.max_age
@@ -63,7 +62,6 @@ def clear_cache(cls, max_age: int = None) -> None:
63
62
 
64
63
  # Go through all files
65
64
  for file in os.listdir(cls.cache_dir + os.sep + cls.cache_subdir):
66
-
67
65
  # Get full path
68
66
  path = os.path.join(cls.cache_dir + os.sep + cls.cache_subdir, file)
69
67
 
@@ -8,16 +8,19 @@ under the terms of the Creative Commons Attribution-NonCommercial
8
8
  The code is licensed under the MIT license.
9
9
  """
10
10
 
11
+ from io import BytesIO
12
+ from gzip import GzipFile
13
+ from urllib.request import Request, ProxyHandler, build_opener
11
14
  from urllib.error import HTTPError
12
15
  from multiprocessing import Pool
13
16
  from multiprocessing.pool import ThreadPool
14
- from typing import Callable, Union
17
+ from typing import Callable, List, Optional
15
18
  import pandas as pd
16
19
  from meteostat.core.warn import warn
17
20
 
18
21
 
19
22
  def processing_handler(
20
- datasets: list, load: Callable[[dict], None], cores: int, threads: int
23
+ datasets: List, load: Callable[[dict], None], cores: int, threads: int
21
24
  ) -> None:
22
25
  """
23
26
  Load multiple datasets (simultaneously)
@@ -28,10 +31,8 @@ def processing_handler(
28
31
 
29
32
  # Multi-core processing
30
33
  if cores > 1 and len(datasets) > 1:
31
-
32
34
  # Create process pool
33
35
  with Pool(cores) as pool:
34
-
35
36
  # Process datasets in pool
36
37
  output = pool.starmap(load, datasets)
37
38
 
@@ -41,10 +42,8 @@ def processing_handler(
41
42
 
42
43
  # Multi-thread processing
43
44
  elif threads > 1 and len(datasets) > 1:
44
-
45
45
  # Create process pool
46
46
  with ThreadPool(threads) as pool:
47
-
48
47
  # Process datasets in pool
49
48
  output = pool.starmap(load, datasets)
50
49
 
@@ -54,12 +53,11 @@ def processing_handler(
54
53
 
55
54
  # Single-thread processing
56
55
  else:
57
-
58
56
  for dataset in datasets:
59
57
  output.append(load(*dataset))
60
58
 
61
59
  # Remove empty DataFrames
62
- filtered = list(filter(lambda df: df.index.size > 0, output))
60
+ filtered = list(filter(lambda df: not df.empty, output))
63
61
 
64
62
  return pd.concat(filtered) if len(filtered) > 0 else output[0]
65
63
 
@@ -67,36 +65,36 @@ def processing_handler(
67
65
  def load_handler(
68
66
  endpoint: str,
69
67
  path: str,
70
- columns: list,
71
- types: Union[dict, None],
72
- parse_dates: list,
73
- coerce_dates: bool = False,
68
+ proxy: Optional[str] = None,
69
+ names: Optional[List] = None,
70
+ dtype: Optional[dict] = None,
71
+ parse_dates: Optional[List] = None,
72
+ default_df: Optional[pd.DataFrame] = None,
74
73
  ) -> pd.DataFrame:
75
74
  """
76
75
  Load a single CSV file into a DataFrame
77
76
  """
78
77
 
79
78
  try:
79
+ handlers = []
80
+
81
+ # Set a proxy
82
+ if proxy:
83
+ handlers.append(ProxyHandler({"http": proxy, "https": proxy}))
80
84
 
81
85
  # Read CSV file from Meteostat endpoint
82
- df = pd.read_csv(
83
- endpoint + path,
84
- compression="gzip",
85
- names=columns,
86
- dtype=types,
87
- parse_dates=parse_dates,
88
- )
89
-
90
- # Force datetime conversion
91
- if coerce_dates:
92
- df.iloc[:, parse_dates] = df.iloc[:, parse_dates].apply(
93
- pd.to_datetime, errors="coerce"
94
- )
86
+ with build_opener(*handlers).open(Request(endpoint + path)) as response:
87
+ # Decompress the content
88
+ with GzipFile(fileobj=BytesIO(response.read()), mode="rb") as file:
89
+ df = pd.read_csv(
90
+ file,
91
+ names=names,
92
+ dtype=dtype,
93
+ parse_dates=parse_dates,
94
+ )
95
95
 
96
96
  except (FileNotFoundError, HTTPError):
97
-
98
- # Create empty DataFrane
99
- df = pd.DataFrame(columns=[*types])
97
+ df = default_df if default_df is not None else pd.DataFrame(columns=names)
100
98
 
101
99
  # Display warning
102
100
  warn(f"Cannot load {path} from {endpoint}")
@@ -16,7 +16,7 @@ def _format(message, category, _filename, _lineno, _line=None) -> str:
16
16
  Print warning on a single line
17
17
  """
18
18
 
19
- return "%s: %s\n" % (category.__name__, message)
19
+ return f"{category.__name__}: {message}\n"
20
20
 
21
21
 
22
22
  # Set warning format
@@ -9,28 +9,31 @@ The code is licensed under the MIT license.
9
9
  """
10
10
 
11
11
  import os
12
+ from typing import Optional
12
13
 
13
14
 
14
15
  class Base:
15
-
16
16
  """
17
17
  Base class that provides features which are used across the package
18
18
  """
19
19
 
20
20
  # Base URL of the Meteostat bulk data interface
21
- endpoint: str = "https://bulk.meteostat.net/v2/"
21
+ endpoint = "https://bulk.meteostat.net/v2/"
22
+
23
+ # Proxy URL for the Meteostat (bulk) data interface
24
+ proxy: Optional[str] = None
22
25
 
23
26
  # Location of the cache directory
24
- cache_dir: str = os.path.expanduser("~") + os.sep + ".meteostat" + os.sep + "cache"
27
+ cache_dir = os.path.expanduser("~") + os.sep + ".meteostat" + os.sep + "cache"
25
28
 
26
29
  # Auto clean cache directories?
27
- autoclean: bool = True
30
+ autoclean = True
28
31
 
29
32
  # Maximum age of a cached file in seconds
30
- max_age: int = 24 * 60 * 60
33
+ max_age = 24 * 60 * 60
31
34
 
32
35
  # Number of processes used for processing files
33
- processes: int = 1
36
+ processes = 1
34
37
 
35
38
  # Number of threads used for processing files
36
- threads: int = 1
39
+ threads = 1
@@ -8,7 +8,7 @@ under the terms of the Creative Commons Attribution-NonCommercial
8
8
  The code is licensed under the MIT license.
9
9
  """
10
10
 
11
- from datetime import datetime
11
+ from datetime import datetime, timedelta
12
12
  from typing import Union
13
13
  import pandas as pd
14
14
  from meteostat.enumerations.granularity import Granularity
@@ -18,33 +18,54 @@ from meteostat.interface.point import Point
18
18
 
19
19
 
20
20
  class Daily(TimeSeries):
21
-
22
21
  """
23
22
  Retrieve daily weather observations for one or multiple weather stations or
24
23
  a single geographical point
25
24
  """
26
25
 
27
26
  # The cache subdirectory
28
- cache_subdir: str = "daily"
27
+ cache_subdir = "daily"
29
28
 
30
29
  # Granularity
31
30
  granularity = Granularity.DAILY
32
31
 
32
+ # Download data as annual chunks
33
+ # This cannot be changed and is only kept for backward compatibility
34
+ chunked = True
35
+
33
36
  # Default frequency
34
- _freq: str = "1D"
37
+ _freq = "1D"
38
+
39
+ # Source mappings
40
+ _source_mappings = {
41
+ "dwd_daily": "A",
42
+ "eccc_daily": "A",
43
+ "ghcnd": "B",
44
+ "dwd_hourly": "C",
45
+ "eccc_hourly": "C",
46
+ "isd_lite": "D",
47
+ "synop": "E",
48
+ "dwd_poi": "E",
49
+ "metar": "F",
50
+ "model": "G",
51
+ "dwd_mosmix": "G",
52
+ "metno_forecast": "G",
53
+ }
35
54
 
36
55
  # Flag which represents model data
37
56
  _model_flag = "G"
38
57
 
39
58
  # Columns
40
- _columns: list = [
41
- "date",
42
- "tavg",
59
+ _columns = [
60
+ "year",
61
+ "month",
62
+ "day",
63
+ {"tavg": "temp"},
43
64
  "tmin",
44
65
  "tmax",
45
66
  "prcp",
46
- "snow",
47
- "wdir",
67
+ {"snow": "snwd"},
68
+ {"wdir": None},
48
69
  "wspd",
49
70
  "wpgt",
50
71
  "pres",
@@ -52,27 +73,13 @@ class Daily(TimeSeries):
52
73
  ]
53
74
 
54
75
  # Index of first meteorological column
55
- _first_met_col = 1
56
-
57
- # Data types
58
- _types: dict = {
59
- "tavg": "float64",
60
- "tmin": "float64",
61
- "tmax": "float64",
62
- "prcp": "float64",
63
- "snow": "float64",
64
- "wdir": "float64",
65
- "wspd": "float64",
66
- "wpgt": "float64",
67
- "pres": "float64",
68
- "tsun": "float64",
69
- }
76
+ _first_met_col = 3
70
77
 
71
78
  # Columns for date parsing
72
- _parse_dates: dict = {"time": [0]}
79
+ _parse_dates = ["year", "month", "day"]
73
80
 
74
81
  # Default aggregation functions
75
- aggregations: dict = {
82
+ aggregations = {
76
83
  "tavg": "mean",
77
84
  "tmin": "min",
78
85
  "tmax": "max",
@@ -88,12 +95,18 @@ class Daily(TimeSeries):
88
95
  def __init__(
89
96
  self,
90
97
  loc: Union[pd.DataFrame, Point, list, str], # Station(s) or geo point
91
- start: datetime = None,
92
- end: datetime = None,
93
- model: bool = True, # Include model data?
94
- flags: bool = False, # Load source flags?
98
+ start=datetime(1781, 1, 1, 0, 0, 0),
99
+ end=datetime.combine(
100
+ datetime.today().date() + timedelta(days=10), datetime.max.time()
101
+ ),
102
+ model=True, # Include model data?
103
+ flags=False, # Load source flags?
95
104
  ) -> None:
96
-
105
+ # Extract relevant years
106
+ if self.chunked:
107
+ self._annual_steps = [
108
+ start.year + i for i in range(end.year - start.year + 1)
109
+ ]
97
110
  # Initialize time series
98
111
  self._init_time_series(loc, start, end, model, flags)
99
112
 
@@ -9,50 +9,66 @@ The code is licensed under the MIT license.
9
9
  """
10
10
 
11
11
  from math import floor
12
- from datetime import datetime
13
- from typing import Union
12
+ from datetime import datetime, timedelta
13
+ from typing import Optional, Union
14
14
  import pytz
15
15
  import pandas as pd
16
16
  from meteostat.enumerations.granularity import Granularity
17
17
  from meteostat.utilities.aggregations import degree_mean
18
18
  from meteostat.interface.timeseries import TimeSeries
19
19
  from meteostat.interface.point import Point
20
+ from meteostat.utilities.mutations import calculate_dwpt
20
21
 
21
22
 
22
23
  class Hourly(TimeSeries):
23
-
24
24
  """
25
25
  Retrieve hourly weather observations for one or multiple weather stations or
26
26
  a single geographical point
27
27
  """
28
28
 
29
29
  # The cache subdirectory
30
- cache_subdir: str = "hourly"
30
+ cache_subdir = "hourly"
31
31
 
32
32
  # Granularity
33
33
  granularity = Granularity.HOURLY
34
34
 
35
35
  # Download data as annual chunks
36
- chunked: bool = True
36
+ # This cannot be changed and is only kept for backward compatibility
37
+ chunked = True
37
38
 
38
39
  # The time zone
39
- _timezone: str = None
40
+ _timezone: Optional[str] = None
40
41
 
41
42
  # Default frequency
42
- _freq: str = "1H"
43
+ _freq = "1h"
44
+
45
+ # Source mappings
46
+ _source_mappings = {
47
+ "metar": "D",
48
+ "model": "E",
49
+ "isd_lite": "B",
50
+ "synop": "C",
51
+ "dwd_poi": "C",
52
+ "dwd_hourly": "A",
53
+ "dwd_mosmix": "E",
54
+ "metno_forecast": "E",
55
+ "eccc_hourly": "A",
56
+ }
43
57
 
44
58
  # Flag which represents model data
45
59
  _model_flag = "E"
46
60
 
47
61
  # Raw data columns
48
- _columns: list = [
49
- "date",
62
+ _columns = [
63
+ "year",
64
+ "month",
65
+ "day",
50
66
  "hour",
51
67
  "temp",
52
- "dwpt",
68
+ {"dwpt": calculate_dwpt},
53
69
  "rhum",
54
70
  "prcp",
55
- "snow",
71
+ {"snow": "snwd"},
56
72
  "wdir",
57
73
  "wspd",
58
74
  "wpgt",
@@ -62,28 +78,13 @@ class Hourly(TimeSeries):
62
78
  ]
63
79
 
64
80
  # Index of first meteorological column
65
- _first_met_col = 2
66
-
67
- # Data types
68
- _types: dict = {
69
- "temp": "float64",
70
- "dwpt": "float64",
71
- "rhum": "float64",
72
- "prcp": "float64",
73
- "snow": "float64",
74
- "wdir": "float64",
75
- "wspd": "float64",
76
- "wpgt": "float64",
77
- "pres": "float64",
78
- "tsun": "float64",
79
- "coco": "float64",
80
- }
81
+ _first_met_col = 4
81
82
 
82
83
  # Columns for date parsing
83
- _parse_dates: dict = {"time": [0, 1]}
84
+ _parse_dates = ["year", "month", "day", "hour"]
84
85
 
85
86
  # Default aggregation functions
86
- aggregations: dict = {
87
+ aggregations = {
87
88
  "temp": "mean",
88
89
  "dwpt": "mean",
89
90
  "rhum": "mean",
@@ -98,22 +99,19 @@ class Hourly(TimeSeries):
98
99
  }
99
100
 
100
101
  def _set_time(
101
- self, start: datetime = None, end: datetime = None, timezone: str = None
102
+ self,
103
+ start: Optional[datetime] = None,
104
+ end: Optional[datetime] = None,
105
+ timezone: Optional[str] = None,
102
106
  ) -> None:
103
107
  """
104
108
  Set & adapt the period's time zone
105
109
  """
106
-
107
- # Don't use chunks if full dataset is requested
108
- if start == None:
109
- self.chunked = False
110
-
111
110
  if timezone:
112
111
  # Save timezone
113
112
  self._timezone = timezone
114
113
 
115
114
  if start and end:
116
-
117
115
  # Initialize time zone
118
116
  timezone = pytz.timezone(self._timezone)
119
117
 
@@ -124,7 +122,9 @@ class Hourly(TimeSeries):
124
122
  end = timezone.localize(end, is_dst=None).astimezone(pytz.utc)
125
123
 
126
124
  if self.chunked:
127
- self._annual_steps = [start.year + i for i in range(end.year - start.year + 1)]
125
+ self._annual_steps = [
126
+ start.year + i for i in range(end.year - start.year + 1)
127
+ ]
128
128
 
129
129
  self._start = start
130
130
  self._end = end
@@ -132,13 +132,14 @@ class Hourly(TimeSeries):
132
132
  def __init__(
133
133
  self,
134
134
  loc: Union[pd.DataFrame, Point, list, str], # Station(s) or geo point
135
- start: datetime = None,
136
- end: datetime = None,
137
- timezone: str = None,
138
- model: bool = True, # Include model data?
139
- flags: bool = False, # Load source flags?
135
+ start=datetime(1890, 1, 1, 0, 0, 0),
136
+ end=datetime.combine(
137
+ datetime.today().date() + timedelta(days=10), datetime.max.time()
138
+ ),
139
+ timezone: Optional[str] = None,
140
+ model=True, # Include model data?
141
+ flags=False, # Load source flags?
140
142
  ) -> None:
141
-
142
143
  # Set time zone and adapt period
143
144
  self._set_time(start, end, timezone)
144
145