env-canada 0.7.2__tar.gz → 0.9.0__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 (31) hide show
  1. {env_canada-0.7.2 → env_canada-0.9.0}/LICENSE +1 -1
  2. {env_canada-0.7.2/env_canada.egg-info → env_canada-0.9.0}/PKG-INFO +75 -25
  3. {env_canada-0.7.2 → env_canada-0.9.0}/README.md +39 -13
  4. env_canada-0.9.0/env_canada/__init__.py +15 -0
  5. env_canada-0.9.0/env_canada/constants.py +1 -0
  6. {env_canada-0.7.2 → env_canada-0.9.0}/env_canada/ec_aqhi.py +6 -6
  7. {env_canada-0.7.2 → env_canada-0.9.0}/env_canada/ec_cache.py +2 -1
  8. {env_canada-0.7.2 → env_canada-0.9.0}/env_canada/ec_historical.py +44 -34
  9. {env_canada-0.7.2 → env_canada-0.9.0}/env_canada/ec_hydro.py +2 -3
  10. {env_canada-0.7.2 → env_canada-0.9.0}/env_canada/ec_radar.py +7 -7
  11. {env_canada-0.7.2 → env_canada-0.9.0}/env_canada/ec_weather.py +161 -113
  12. {env_canada-0.7.2 → env_canada-0.9.0/env_canada.egg-info}/PKG-INFO +75 -25
  13. {env_canada-0.7.2 → env_canada-0.9.0}/env_canada.egg-info/SOURCES.txt +2 -7
  14. env_canada-0.9.0/env_canada.egg-info/requires.txt +9 -0
  15. env_canada-0.9.0/pyproject.toml +72 -0
  16. env_canada-0.7.2/env_canada/__init__.py +0 -5
  17. env_canada-0.7.2/env_canada/constants.py +0 -1
  18. env_canada-0.7.2/env_canada.egg-info/requires.txt +0 -10
  19. env_canada-0.7.2/setup.py +0 -35
  20. env_canada-0.7.2/tests/test_ec_aqhi.py +0 -34
  21. env_canada-0.7.2/tests/test_ec_historical.py +0 -30
  22. env_canada-0.7.2/tests/test_ec_hydro.py +0 -37
  23. env_canada-0.7.2/tests/test_ec_radar.py +0 -56
  24. env_canada-0.7.2/tests/test_ec_weather.py +0 -28
  25. {env_canada-0.7.2 → env_canada-0.9.0}/MANIFEST.in +0 -0
  26. {env_canada-0.7.2 → env_canada-0.9.0}/env_canada/10x20.pbm +0 -0
  27. {env_canada-0.7.2 → env_canada-0.9.0}/env_canada/10x20.pil +0 -0
  28. {env_canada-0.7.2 → env_canada-0.9.0}/env_canada/ec_exc.py +0 -0
  29. {env_canada-0.7.2 → env_canada-0.9.0}/env_canada.egg-info/dependency_links.txt +0 -0
  30. {env_canada-0.7.2 → env_canada-0.9.0}/env_canada.egg-info/top_level.txt +0 -0
  31. {env_canada-0.7.2 → env_canada-0.9.0}/setup.cfg +0 -0
@@ -16,4 +16,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
16
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
17
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
18
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
- SOFTWARE
19
+ SOFTWARE
@@ -1,34 +1,72 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: env_canada
3
- Version: 0.7.2
3
+ Version: 0.9.0
4
4
  Summary: A package to access meteorological data from Environment Canada
5
- Home-page: https://github.com/michaeldavie/env_canada
6
- Author: Michael Davie
7
- Author-email: michael.davie@gmail.com
8
- License: MIT
5
+ Author-email: Michael Davie <michael.davie@gmail.com>
6
+ Maintainer-email: Michael Davie <michael.davie@gmail.com>
7
+ License: Copyright (c) 2018 The Python Packaging Authority
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE
26
+
27
+ Project-URL: Homepage, https://github.com/michaeldavie/env_canada
28
+ Project-URL: Documentation, https://github.com/michaeldavie/env_canada
29
+ Project-URL: Repository, https://github.com/michaeldavie/env_canada
30
+ Project-URL: Issues, https://github.com/michaeldavie/env_canada/issues
31
+ Project-URL: Changelog, https://github.com/michaeldavie/env_canada/blob/master/CHANGELOG.md
9
32
  Classifier: Programming Language :: Python :: 3
10
33
  Classifier: License :: OSI Approved :: MIT License
11
34
  Classifier: Operating System :: OS Independent
35
+ Requires-Python: >=3.11
12
36
  Description-Content-Type: text/markdown
13
37
  License-File: LICENSE
14
38
  Requires-Dist: aiohttp>=3.9.0
15
- Requires-Dist: defusedxml
16
- Requires-Dist: geopy
39
+ Requires-Dist: geopy>=2.4.1
17
40
  Requires-Dist: imageio>=2.28.0
18
- Requires-Dist: lxml
41
+ Requires-Dist: lxml>=5.3.0
19
42
  Requires-Dist: numpy>=1.22.2
20
- Requires-Dist: pandas>=1.3.0
43
+ Requires-Dist: pandas>=2.2.3
21
44
  Requires-Dist: Pillow>=10.0.1
22
- Requires-Dist: python-dateutil
23
- Requires-Dist: voluptuous
45
+ Requires-Dist: python-dateutil>=2.9
46
+ Requires-Dist: voluptuous>=0.15.2
47
+ Dynamic: license-file
24
48
 
25
49
  # Environment Canada (env_canada)
26
50
 
27
51
  [![PyPI version](https://badge.fury.io/py/env-canada.svg)](https://badge.fury.io/py/env-canada)
28
- [![Snyk rating](https://snyk-widget.herokuapp.com/badge/pip/env-canada/badge.svg)](https://snyk.io/vuln/pip:env-canada@0.7.2?utm_source=badge)
52
+ [![Snyk rating](https://snyk-widget.herokuapp.com/badge/pip/env-canada/badge.svg)](https://snyk.io/vuln/pip:env-canada@0.8.0?utm_source=badge)
53
+ [![Python Lint and Test](../..//actions/workflows/python-app.yml/badge.svg)](../../actions/workflows/python-app.yml)
29
54
 
30
55
  This package provides access to various data sources published by [Environment and Climate Change Canada](https://www.canada.ca/en/environment-climate-change.html).
31
56
 
57
+ > [!IMPORTANT]
58
+ > If you're using the library in a Jupyter notebook, replace `asyncio.run(...)` with `await ...` in the examples below. For example:
59
+ >
60
+ > ```python
61
+ > asyncio.run(ec_en.update())
62
+ > ```
63
+ >
64
+ > becomes
65
+ >
66
+ > ```python
67
+ > await ec_en.update()
68
+ > ```
69
+
32
70
  ## Weather Observations and Forecasts
33
71
 
34
72
  `ECWeather` provides current conditions and forecasts. It automatically determines which weather station to use based on latitude/longitude provided. It is also possible to specify a specific station code of the form `AB/s0000123` based on those listed in [this CSV file](https://dd.weather.gc.ca/citypage_weather/docs/site_list_towns_en.csv). For example:
@@ -39,7 +77,7 @@ import asyncio
39
77
  from env_canada import ECWeather
40
78
 
41
79
  ec_en = ECWeather(coordinates=(50, -100))
42
- ec_fr = ECWeather(station_id='ON/s0000430', language='french')
80
+ ec_fr = ECWeather(station_id="ON/s0000430", language="french")
43
81
 
44
82
  asyncio.run(ec_en.update())
45
83
 
@@ -120,7 +158,7 @@ from env_canada import ECHistorical
120
158
  from env_canada.ec_historical import get_historical_stations
121
159
 
122
160
  # search for stations, response contains station_ids
123
- coordinates = [53.916944, -122.749444] # [lat, long]
161
+ coordinates = [53.916944, -122.749444] # [lat, long]
124
162
 
125
163
  # coordinates: [lat, long]
126
164
  # radius: km
@@ -136,8 +174,12 @@ ec_fr_csv = ECHistorical(station_id=31688, year=2020, language="french", format=
136
174
  # timeframe argument can be passed to change the granularity
137
175
  # timeframe=1 hourly (need to create of for every month in that case, use ECHistoricalRange to handle it automatically)
138
176
  # timeframe=2 daily (default)
139
- ec_en_xml = ECHistorical(station_id=31688, year=2020, month=1, language="english", format="xml", timeframe=1)
140
- ec_en_csv = ECHistorical(station_id=31688, year=2020, month=1, language="english", format="csv", timeframe=1)
177
+ ec_en_xml = ECHistorical(
178
+ station_id=31688, year=2020, month=1, language="english", format="xml", timeframe=1
179
+ )
180
+ ec_en_csv = ECHistorical(
181
+ station_id=31688, year=2020, month=1, language="english", format="csv", timeframe=1
182
+ )
141
183
 
142
184
  asyncio.run(ec_en_xml.update())
143
185
  asyncio.run(ec_en_csv.update())
@@ -150,8 +192,8 @@ ec_en_xml.station_data
150
192
 
151
193
  # csv-generated responses return csv-like station data
152
194
  import pandas as pd
153
- df = pd.read_csv(ec_en_csv.station_data)
154
195
 
196
+ df = pd.read_csv(ec_en_csv.station_data)
155
197
  ```
156
198
 
157
199
  `ECHistoricalRange` provides historical weather data within a specific range and handles the update by itself.
@@ -170,21 +212,29 @@ from env_canada import ECHistoricalRange
170
212
  from env_canada.ec_historical import get_historical_stations
171
213
  from datetime import datetime
172
214
 
173
- coordinates = ['48.508333', '-68.467667']
215
+ coordinates = ["48.508333", "-68.467667"]
174
216
 
175
- stations = pd.DataFrame(asyncio.run(get_historical_stations(coordinates, start_year=2022,
176
- end_year=2022, radius=200, limit=100))).T
217
+ stations = pd.DataFrame(
218
+ asyncio.run(
219
+ get_historical_stations(
220
+ coordinates, start_year=2022, end_year=2022, radius=200, limit=100
221
+ )
222
+ )
223
+ ).T
177
224
 
178
- ec = ECHistoricalRange(station_id=int(stations.iloc[0,2]), timeframe="daily",
179
- daterange=(datetime(2022, 7, 1, 12, 12), datetime(2022, 8, 1, 12, 12)))
225
+ ec = ECHistoricalRange(
226
+ station_id=int(stations.iloc[0, 2]),
227
+ timeframe="daily",
228
+ daterange=(datetime(2022, 7, 1, 12, 12), datetime(2022, 8, 1, 12, 12)),
229
+ )
180
230
 
181
231
  ec.get_data()
182
232
 
183
- #yield an XML formated str.
233
+ # yield an XML formated str.
184
234
  # For more options, use ec.to_xml(*arg, **kwargs) with pandas options
185
235
  ec.xml
186
236
 
187
- #yield an CSV formated str.
237
+ # yield an CSV formated str.
188
238
  # For more options, use ec.to_csv(*arg, **kwargs) with pandas options
189
239
  ec.csv
190
240
  ```
@@ -1,10 +1,24 @@
1
1
  # Environment Canada (env_canada)
2
2
 
3
3
  [![PyPI version](https://badge.fury.io/py/env-canada.svg)](https://badge.fury.io/py/env-canada)
4
- [![Snyk rating](https://snyk-widget.herokuapp.com/badge/pip/env-canada/badge.svg)](https://snyk.io/vuln/pip:env-canada@0.7.2?utm_source=badge)
4
+ [![Snyk rating](https://snyk-widget.herokuapp.com/badge/pip/env-canada/badge.svg)](https://snyk.io/vuln/pip:env-canada@0.8.0?utm_source=badge)
5
+ [![Python Lint and Test](../..//actions/workflows/python-app.yml/badge.svg)](../../actions/workflows/python-app.yml)
5
6
 
6
7
  This package provides access to various data sources published by [Environment and Climate Change Canada](https://www.canada.ca/en/environment-climate-change.html).
7
8
 
9
+ > [!IMPORTANT]
10
+ > If you're using the library in a Jupyter notebook, replace `asyncio.run(...)` with `await ...` in the examples below. For example:
11
+ >
12
+ > ```python
13
+ > asyncio.run(ec_en.update())
14
+ > ```
15
+ >
16
+ > becomes
17
+ >
18
+ > ```python
19
+ > await ec_en.update()
20
+ > ```
21
+
8
22
  ## Weather Observations and Forecasts
9
23
 
10
24
  `ECWeather` provides current conditions and forecasts. It automatically determines which weather station to use based on latitude/longitude provided. It is also possible to specify a specific station code of the form `AB/s0000123` based on those listed in [this CSV file](https://dd.weather.gc.ca/citypage_weather/docs/site_list_towns_en.csv). For example:
@@ -15,7 +29,7 @@ import asyncio
15
29
  from env_canada import ECWeather
16
30
 
17
31
  ec_en = ECWeather(coordinates=(50, -100))
18
- ec_fr = ECWeather(station_id='ON/s0000430', language='french')
32
+ ec_fr = ECWeather(station_id="ON/s0000430", language="french")
19
33
 
20
34
  asyncio.run(ec_en.update())
21
35
 
@@ -96,7 +110,7 @@ from env_canada import ECHistorical
96
110
  from env_canada.ec_historical import get_historical_stations
97
111
 
98
112
  # search for stations, response contains station_ids
99
- coordinates = [53.916944, -122.749444] # [lat, long]
113
+ coordinates = [53.916944, -122.749444] # [lat, long]
100
114
 
101
115
  # coordinates: [lat, long]
102
116
  # radius: km
@@ -112,8 +126,12 @@ ec_fr_csv = ECHistorical(station_id=31688, year=2020, language="french", format=
112
126
  # timeframe argument can be passed to change the granularity
113
127
  # timeframe=1 hourly (need to create of for every month in that case, use ECHistoricalRange to handle it automatically)
114
128
  # timeframe=2 daily (default)
115
- ec_en_xml = ECHistorical(station_id=31688, year=2020, month=1, language="english", format="xml", timeframe=1)
116
- ec_en_csv = ECHistorical(station_id=31688, year=2020, month=1, language="english", format="csv", timeframe=1)
129
+ ec_en_xml = ECHistorical(
130
+ station_id=31688, year=2020, month=1, language="english", format="xml", timeframe=1
131
+ )
132
+ ec_en_csv = ECHistorical(
133
+ station_id=31688, year=2020, month=1, language="english", format="csv", timeframe=1
134
+ )
117
135
 
118
136
  asyncio.run(ec_en_xml.update())
119
137
  asyncio.run(ec_en_csv.update())
@@ -126,8 +144,8 @@ ec_en_xml.station_data
126
144
 
127
145
  # csv-generated responses return csv-like station data
128
146
  import pandas as pd
129
- df = pd.read_csv(ec_en_csv.station_data)
130
147
 
148
+ df = pd.read_csv(ec_en_csv.station_data)
131
149
  ```
132
150
 
133
151
  `ECHistoricalRange` provides historical weather data within a specific range and handles the update by itself.
@@ -146,21 +164,29 @@ from env_canada import ECHistoricalRange
146
164
  from env_canada.ec_historical import get_historical_stations
147
165
  from datetime import datetime
148
166
 
149
- coordinates = ['48.508333', '-68.467667']
167
+ coordinates = ["48.508333", "-68.467667"]
150
168
 
151
- stations = pd.DataFrame(asyncio.run(get_historical_stations(coordinates, start_year=2022,
152
- end_year=2022, radius=200, limit=100))).T
169
+ stations = pd.DataFrame(
170
+ asyncio.run(
171
+ get_historical_stations(
172
+ coordinates, start_year=2022, end_year=2022, radius=200, limit=100
173
+ )
174
+ )
175
+ ).T
153
176
 
154
- ec = ECHistoricalRange(station_id=int(stations.iloc[0,2]), timeframe="daily",
155
- daterange=(datetime(2022, 7, 1, 12, 12), datetime(2022, 8, 1, 12, 12)))
177
+ ec = ECHistoricalRange(
178
+ station_id=int(stations.iloc[0, 2]),
179
+ timeframe="daily",
180
+ daterange=(datetime(2022, 7, 1, 12, 12), datetime(2022, 8, 1, 12, 12)),
181
+ )
156
182
 
157
183
  ec.get_data()
158
184
 
159
- #yield an XML formated str.
185
+ # yield an XML formated str.
160
186
  # For more options, use ec.to_xml(*arg, **kwargs) with pandas options
161
187
  ec.xml
162
188
 
163
- #yield an CSV formated str.
189
+ # yield an CSV formated str.
164
190
  # For more options, use ec.to_csv(*arg, **kwargs) with pandas options
165
191
  ec.csv
166
192
  ```
@@ -0,0 +1,15 @@
1
+ __all__ = [
2
+ "ECAirQuality",
3
+ "ECHistorical",
4
+ "ECHistoricalRange",
5
+ "ECHydro",
6
+ "ECRadar",
7
+ "ECWeather",
8
+ "ECWeatherUpdateFailed",
9
+ ]
10
+
11
+ from .ec_aqhi import ECAirQuality
12
+ from .ec_historical import ECHistorical, ECHistoricalRange
13
+ from .ec_hydro import ECHydro
14
+ from .ec_radar import ECRadar
15
+ from .ec_weather import ECWeather, ECWeatherUpdateFailed
@@ -0,0 +1 @@
1
+ USER_AGENT = "env_canada/0.8.0"
@@ -1,10 +1,10 @@
1
- from datetime import datetime, timezone
2
1
  import logging
2
+ from datetime import datetime, timezone
3
3
 
4
+ import voluptuous as vol
4
5
  from aiohttp import ClientSession
5
- import defusedxml.ElementTree as et
6
6
  from geopy import distance
7
- import voluptuous as vol
7
+ from lxml import etree as et
8
8
 
9
9
  from .constants import USER_AGENT
10
10
 
@@ -43,7 +43,7 @@ async def get_aqhi_regions(language):
43
43
  )
44
44
  result = await response.read()
45
45
 
46
- site_xml = result.decode("utf-8")
46
+ site_xml = result
47
47
  xml_object = et.fromstring(site_xml)
48
48
 
49
49
  for zone in xml_object.findall("./EC_administrativeZone"):
@@ -83,7 +83,7 @@ async def find_closest_region(language, lat, lon):
83
83
  return min(region_list, key=site_distance)
84
84
 
85
85
 
86
- class ECAirQuality(object):
86
+ class ECAirQuality:
87
87
  """Get air quality data from Environment Canada."""
88
88
 
89
89
  def __init__(self, **kwargs):
@@ -152,7 +152,7 @@ class ECAirQuality(object):
152
152
  return None
153
153
 
154
154
  result = await response.read()
155
- aqhi_xml = result.decode("ISO-8859-1")
155
+ aqhi_xml = result
156
156
  return et.fromstring(aqhi_xml)
157
157
 
158
158
  async def update(self):
@@ -1,8 +1,9 @@
1
1
  from datetime import datetime
2
+ from typing import Any, ClassVar
2
3
 
3
4
 
4
5
  class Cache:
5
- _cache = {}
6
+ _cache: ClassVar[dict[str, tuple[datetime, Any]]] = {}
6
7
 
7
8
  @classmethod
8
9
  def add(cls, cache_key, item, cache_time):
@@ -1,26 +1,28 @@
1
+ import asyncio
1
2
  import copy
2
3
  import csv
4
+ import logging
3
5
  from datetime import datetime
4
- from dateutil.relativedelta import relativedelta
5
6
  from io import StringIO
6
- import logging
7
- import xml.etree.ElementTree as et
8
- import pandas as pd
9
- import asyncio
10
7
 
11
- from aiohttp import ClientSession
12
- from dateutil import parser, tz
13
- import defusedxml.ElementTree as et
14
8
  import lxml.html
9
+ import pandas as pd
15
10
  import voluptuous as vol
11
+ from aiohttp import ClientSession
12
+ from dateutil import parser, tz
13
+ from dateutil.relativedelta import relativedelta
14
+ from lxml import etree as et
16
15
 
17
16
  from .constants import USER_AGENT
18
17
 
19
-
20
18
  STATIONS_URL = "https://climate.weather.gc.ca/historical_data/search_historic_data_stations_{}.html"
21
19
 
22
20
  WEATHER_URL = "https://climate.weather.gc.ca/climate_data/bulk_data_{}.html"
23
21
 
22
+ _TODAY = datetime.today().date()
23
+ _ONE_YEAR_AGO = _TODAY - relativedelta(years=1, months=1, day=1)
24
+ _YEAR = datetime.today().year
25
+
24
26
  LOG = logging.getLogger(__name__)
25
27
 
26
28
  __all__ = ["ECHistorical"]
@@ -126,7 +128,7 @@ async def get_historical_stations(
126
128
  coordinates,
127
129
  radius=25,
128
130
  start_year=1840,
129
- end_year=datetime.today().year,
131
+ end_year=_YEAR,
130
132
  limit=25,
131
133
  language="english",
132
134
  timeframe=2,
@@ -204,8 +206,7 @@ async def get_historical_stations(
204
206
  return stations
205
207
 
206
208
 
207
- class ECHistorical(object):
208
-
209
+ class ECHistorical:
209
210
  """Get historical weather data from Environment Canada."""
210
211
 
211
212
  def __init__(self, **kwargs):
@@ -353,30 +354,30 @@ def flip_daterange(f):
353
354
  return wrapper
354
355
 
355
356
 
356
- class ECHistoricalRange(object):
357
+ class ECHistoricalRange:
357
358
  """Get historical weather data from Environment Canada in the given range for the given station.
358
359
 
359
- options are daily or hourly data
360
+ options are daily or hourly data
360
361
 
361
- Example:
362
- import pandas as pd
363
- import asyncio
364
- from env_canada import ECHistoricalRange, get_historical_stations
365
- from datetime import datetime
362
+ Example:
363
+ import pandas as pd
364
+ import asyncio
365
+ from env_canada import ECHistoricalRange, get_historical_stations
366
+ from datetime import datetime
366
367
 
367
- coordinates = ['48.508333', '-68.467667']
368
+ coordinates = ['48.508333', '-68.467667']
368
369
 
369
- stations = pd.DataFrame(asyncio.run(get_historical_stations(coordinates, start_year=2022,
370
- end_year=2022, radius=200, limit=100))).T
370
+ stations = pd.DataFrame(asyncio.run(get_historical_stations(coordinates, start_year=2022,
371
+ end_year=2022, radius=200, limit=100))).T
371
372
 
372
- ec = ECHistoricalRange(station_id=int(stations.iloc[0,2]), timeframe="hourly",
373
- daterange=(datetime(2022, 7, 1, 12, 12), datetime(2022, 8, 1, 12, 12)))
373
+ ec = ECHistoricalRange(station_id=int(stations.iloc[0,2]), timeframe="hourly",
374
+ daterange=(datetime(2022, 7, 1, 12, 12), datetime(2022, 8, 1, 12, 12)))
374
375
 
375
- ec.get_data()
376
+ ec.get_data()
376
377
 
377
- ec.xml #yield an XML formatted str. For more options, use ec.to_xml(*arg, **kwargs) with pandas options
378
-
379
- ec.csv #yield an CSV formatted str. For more options, use ec.to_csv(*arg, **kwargs) with pandas options
378
+ ec.xml #yield an XML formatted str. For more options, use ec.to_xml(*arg, **kwargs) with pandas options
379
+ =
380
+ ec.csv #yield an CSV formatted str. For more options, use ec.to_csv(*arg, **kwargs) with pandas options
380
381
  """
381
382
 
382
383
  @flip_daterange
@@ -384,8 +385,8 @@ class ECHistoricalRange(object):
384
385
  self,
385
386
  station_id,
386
387
  daterange=(
387
- datetime.today().date() - relativedelta(years=1, months=1, day=1),
388
- datetime.today().date(),
388
+ _ONE_YEAR_AGO,
389
+ _TODAY,
389
390
  ),
390
391
  language="english",
391
392
  timeframe="daily",
@@ -409,7 +410,14 @@ class ECHistoricalRange(object):
409
410
  self.months = self.monthlist(daterange=daterange)
410
411
  self.language = language
411
412
  _tf = {"hourly": 1, "daily": 2, "monthly": 3}
412
- self.timeframe = _tf[timeframe]
413
+ timeframe_int = _tf[timeframe]
414
+ if timeframe_int == 2:
415
+ # prune the months list so it only has unique years. if daily is selected.
416
+ years = set()
417
+ for year, _ in self.months:
418
+ years.add(year)
419
+ self.months = [(year, 1) for year in years]
420
+ self.timeframe = timeframe_int
413
421
 
414
422
  def get_data(self):
415
423
  """
@@ -419,7 +427,6 @@ class ECHistoricalRange(object):
419
427
  """
420
428
  if not self.df.empty:
421
429
  self.df = pd.DataFrame()
422
-
423
430
  ec = [
424
431
  ECHistorical(
425
432
  station_id=self.station_id,
@@ -437,7 +444,7 @@ class ECHistoricalRange(object):
437
444
  self.df = pd.concat((self.df, pd.read_csv(data.station_data)))
438
445
 
439
446
  self.df = self.df.set_index(
440
- self.df.filter(regex="Date/*", axis=1).columns.values[0]
447
+ self.df.filter(regex="Date/*", axis=1).columns.to_numpy()[0]
441
448
  )
442
449
  self.df.index = pd.to_datetime(self.df.index)
443
450
 
@@ -477,7 +484,10 @@ class ECHistoricalRange(object):
477
484
  @flip_daterange
478
485
  def monthlist(self, daterange):
479
486
  startdate, stopdate = daterange
480
- total_months = lambda dt: dt.month + 12 * dt.year
487
+
488
+ def total_months(dt):
489
+ return dt.month + 12 * dt.year
490
+
481
491
  mlist = []
482
492
  for tot_m in range(total_months(startdate) - 1, total_months(stopdate)):
483
493
  y, m = divmod(tot_m, 12)
@@ -1,10 +1,10 @@
1
1
  import csv
2
2
  import io
3
3
 
4
+ import voluptuous as vol
4
5
  from aiohttp import ClientSession
5
6
  from dateutil.parser import isoparse
6
7
  from geopy import distance
7
- import voluptuous as vol
8
8
 
9
9
  from .constants import USER_AGENT
10
10
 
@@ -54,8 +54,7 @@ async def closest_site(lat, lon):
54
54
  return closest
55
55
 
56
56
 
57
- class ECHydro(object):
58
-
57
+ class ECHydro:
59
58
  """Get hydrometric data from Environment Canada."""
60
59
 
61
60
  def __init__(self, **kwargs):
@@ -1,16 +1,16 @@
1
1
  import asyncio
2
- from datetime import date, timedelta
3
2
  import logging
4
3
  import math
5
4
  import os
5
+ from datetime import date, timedelta
6
6
  from io import BytesIO
7
7
  from typing import cast
8
8
 
9
9
  import dateutil.parser
10
- import defusedxml.ElementTree as et
11
10
  import voluptuous as vol
12
11
  from aiohttp import ClientSession
13
12
  from aiohttp.client_exceptions import ClientConnectorError
13
+ from lxml import etree as et
14
14
  from PIL import Image, ImageDraw, ImageFont
15
15
 
16
16
  from .constants import USER_AGENT
@@ -114,7 +114,7 @@ async def _get_resource(url, params, bytes=True):
114
114
  return await response.text()
115
115
 
116
116
 
117
- class ECRadar(object):
117
+ class ECRadar:
118
118
  def __init__(self, **kwargs):
119
119
  """Initialize the radar object."""
120
120
 
@@ -195,7 +195,7 @@ class ECRadar(object):
195
195
  return Cache.add("basemap", base_bytes, timedelta(days=7))
196
196
 
197
197
  except ClientConnectorError as e:
198
- logging.warning("Map from %s could not be retrieved: %s" % map_url, e)
198
+ logging.warning("Map from %s could not be retrieved: %s", map_url, e)
199
199
 
200
200
  async def _get_legend(self):
201
201
  """Fetch legend image."""
@@ -226,7 +226,7 @@ class ECRadar(object):
226
226
  if not (capabilities_xml := Cache.get(capabilities_cache_key)):
227
227
  capabilities_params["layer"] = precip_layers[self._precip_type_actual]
228
228
  capabilities_xml = await _get_resource(
229
- geomet_url, capabilities_params, bytes=False
229
+ geomet_url, capabilities_params, bytes=True
230
230
  )
231
231
  Cache.add(capabilities_cache_key, capabilities_xml, timedelta(minutes=5))
232
232
 
@@ -236,9 +236,9 @@ class ECRadar(object):
236
236
  )
237
237
  if dimension_string is not None:
238
238
  if dimension_string := dimension_string.text:
239
- start, end = [
239
+ start, end = (
240
240
  dateutil.parser.isoparse(t) for t in dimension_string.split("/")[:2]
241
- ]
241
+ )
242
242
  self.timestamp = end.isoformat()
243
243
  return (start, end)
244
244
  return None