open-meteo 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/open_meteo/application_contract.rb +26 -0
- data/lib/open_meteo/forecast/variables/general.rb +17 -11
- data/lib/open_meteo/forecast/variables/general_contract.rb +286 -0
- data/lib/open_meteo/forecast.rb +6 -4
- data/lib/open_meteo/location.rb +6 -9
- data/lib/open_meteo/location_contract.rb +14 -0
- data/lib/open_meteo/types.rb +13 -0
- data/lib/open_meteo/version.rb +1 -1
- data/lib/open_meteo.rb +2 -1
- metadata +46 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c4d1b8d00c232c9cd399effc4246167fc6bdcf848cbe77d6ca4c84fa177a0979
|
4
|
+
data.tar.gz: 5e87b95a9ed8876b46bfce225fde195f2e43f5deb7b816e6271eacf76db54968
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 36ac57fc2acb0062996b48c3736058e2d7b2e4ab29dd4eea3ea9857d768bbf4a14a50a5496d44c69a1b307daf8c5d38e600a3f6b4bc359cd6084eebc646a0eec
|
7
|
+
data.tar.gz: a71d7e7e08144eb2dc61c850bb45e66bf1614031bddc8405c6babeb02a46572457d9796c6387b27fb43882355d514676c5eaa092e1dec97ab5f3e8c1dd6f5196
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module OpenMeteo
|
2
|
+
##
|
3
|
+
# Shared functionality for dry contracts.
|
4
|
+
class ApplicationContract < Dry::Validation::Contract
|
5
|
+
##
|
6
|
+
# A validation error that can be raised on contract validation.
|
7
|
+
class ValidationError < StandardError
|
8
|
+
attr_reader :result, :errors
|
9
|
+
|
10
|
+
def initialize(result)
|
11
|
+
@errors = result.errors
|
12
|
+
first_error_key = result.errors.first.path.first
|
13
|
+
first_error_value = result[first_error_key]
|
14
|
+
|
15
|
+
super(
|
16
|
+
"Validation failed: :#{first_error_key} is #{first_error_value} but #{errors.first.text}",
|
17
|
+
)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.validate!(object)
|
22
|
+
result = new.call(object)
|
23
|
+
raise ValidationError, result unless result.errors.empty?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require_relative "general_contract"
|
2
|
+
|
1
3
|
module OpenMeteo
|
2
4
|
class Forecast
|
3
5
|
module Variables
|
@@ -5,18 +7,22 @@ module OpenMeteo
|
|
5
7
|
# The Variables for a general request (meaning without a specific model) to the OpenMeteo API.
|
6
8
|
#
|
7
9
|
# See https://open-meteo.com/en/docs
|
8
|
-
class General
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
10
|
+
class General < Dry::Struct
|
11
|
+
attribute(
|
12
|
+
:current,
|
13
|
+
OpenMeteo::Types::Strict::Array.of(OpenMeteo::Types::Strict::Symbol).default([].freeze),
|
14
|
+
)
|
15
|
+
attribute(
|
16
|
+
:hourly,
|
17
|
+
OpenMeteo::Types::Strict::Array.of(OpenMeteo::Types::Strict::Symbol).default([].freeze),
|
18
|
+
)
|
19
|
+
attribute(
|
20
|
+
:daily,
|
21
|
+
OpenMeteo::Types::Strict::Array.of(OpenMeteo::Types::Strict::Symbol).default([].freeze),
|
22
|
+
)
|
16
23
|
|
17
|
-
def validate
|
18
|
-
|
19
|
-
true
|
24
|
+
def validate!
|
25
|
+
GeneralContract.validate!(to_hash)
|
20
26
|
end
|
21
27
|
|
22
28
|
def to_get_params
|
@@ -0,0 +1,286 @@
|
|
1
|
+
module OpenMeteo
|
2
|
+
class Forecast
|
3
|
+
module Variables
|
4
|
+
##
|
5
|
+
# Validation contract for OpenMeteo::Forecast::Variables::General.
|
6
|
+
#
|
7
|
+
# See https://open-meteo.com/en/docs
|
8
|
+
class GeneralContract < OpenMeteo::ApplicationContract # rubocop:disable Metrics/ClassLength
|
9
|
+
params do
|
10
|
+
required(:current).array(:symbol)
|
11
|
+
required(:hourly).array(:symbol)
|
12
|
+
required(:daily).array(:symbol)
|
13
|
+
end
|
14
|
+
|
15
|
+
CURRENT_VARIABALES = %i[
|
16
|
+
apparent_temperature
|
17
|
+
cloudcover
|
18
|
+
is_day
|
19
|
+
precipitation
|
20
|
+
pressure_msl
|
21
|
+
rain
|
22
|
+
relativehumidity_2m
|
23
|
+
showers
|
24
|
+
snowfall
|
25
|
+
surface_pressure
|
26
|
+
temperature_2m
|
27
|
+
weathercode
|
28
|
+
winddirection_10m
|
29
|
+
windgusts_10m
|
30
|
+
windspeed_10m
|
31
|
+
].freeze
|
32
|
+
|
33
|
+
HOURLY_VARIABLES_BASE = %i[
|
34
|
+
apparent_temperature
|
35
|
+
cloudcover
|
36
|
+
cloudcover_high
|
37
|
+
cloudcover_low
|
38
|
+
cloudcover_mid
|
39
|
+
dewpoint_2m
|
40
|
+
et0_fao_evapotranspiration
|
41
|
+
evapotranspiration
|
42
|
+
precipitation
|
43
|
+
precipitation_probability
|
44
|
+
pressure_msl
|
45
|
+
rain
|
46
|
+
relativehumidity_2m
|
47
|
+
showers
|
48
|
+
snow_depth
|
49
|
+
snowfall
|
50
|
+
soil_moisture_0_to_1cm
|
51
|
+
soil_moisture_1_to_3cm
|
52
|
+
soil_moisture_27_to_81cm
|
53
|
+
soil_moisture_3_to_9cm
|
54
|
+
soil_moisture_9_to_27cm
|
55
|
+
soil_temperature_0cm
|
56
|
+
soil_temperature_18cm
|
57
|
+
soil_temperature_54cm
|
58
|
+
soil_temperature_6cm
|
59
|
+
surface_pressure
|
60
|
+
temperature_120m
|
61
|
+
temperature_180m
|
62
|
+
temperature_2m
|
63
|
+
temperature_80m
|
64
|
+
vapor_pressure_deficit
|
65
|
+
visibility
|
66
|
+
weathercode
|
67
|
+
winddirection_10m
|
68
|
+
winddirection_120m
|
69
|
+
winddirection_180m
|
70
|
+
winddirection_80m
|
71
|
+
windgusts_10m
|
72
|
+
windspeed_10m
|
73
|
+
windspeed_120m
|
74
|
+
windspeed_180m
|
75
|
+
windspeed_80m
|
76
|
+
].freeze
|
77
|
+
|
78
|
+
HOURLY_VARIABLES_ADDITIONAL = %i[
|
79
|
+
uv_index
|
80
|
+
uv_index_clear_sky
|
81
|
+
is_day
|
82
|
+
cape
|
83
|
+
freezinglevel_height
|
84
|
+
].freeze
|
85
|
+
|
86
|
+
HOURLY_VARIABLES_SOLAR_RADIATION = %i[
|
87
|
+
shortwave_radiation
|
88
|
+
direct_radiation
|
89
|
+
diffuse_radiation
|
90
|
+
direct_normal_irradiance
|
91
|
+
terrestrial_radiation
|
92
|
+
shortwave_radiation_instant
|
93
|
+
direct_radiation_instant
|
94
|
+
diffuse_radiation_instant
|
95
|
+
direct_normal_irradiance_instant
|
96
|
+
terrestrial_radiation_instant
|
97
|
+
].freeze
|
98
|
+
|
99
|
+
HOURLY_VARIABLES_PRESSURE_LEVEL_TEMPERATURE = %i[
|
100
|
+
temperature_1000hPa
|
101
|
+
temperature_975hPa
|
102
|
+
temperature_950hPa
|
103
|
+
temperature_925hPa
|
104
|
+
temperature_900hPa
|
105
|
+
temperature_850hPa
|
106
|
+
temperature_800hPa
|
107
|
+
temperature_700hPa
|
108
|
+
temperature_600hPa
|
109
|
+
temperature_500hPa
|
110
|
+
temperature_400hPa
|
111
|
+
temperature_300hPa
|
112
|
+
temperature_250hPa
|
113
|
+
temperature_200hPa
|
114
|
+
temperature_150hPa
|
115
|
+
temperature_100hPa
|
116
|
+
temperature_70hPa
|
117
|
+
temperature_50hPa
|
118
|
+
temperature_30hPa
|
119
|
+
].freeze
|
120
|
+
|
121
|
+
HOURLY_VARIABLES_PRESSURE_LEVEL_RELATIVE_HUMIDITY = %i[
|
122
|
+
relativehumidity_1000hPa
|
123
|
+
relativehumidity_975hPa
|
124
|
+
relativehumidity_950hPa
|
125
|
+
relativehumidity_925hPa
|
126
|
+
relativehumidity_900hPa
|
127
|
+
relativehumidity_850hPa
|
128
|
+
relativehumidity_800hPa
|
129
|
+
relativehumidity_700hPa
|
130
|
+
relativehumidity_600hPa
|
131
|
+
relativehumidity_500hPa
|
132
|
+
relativehumidity_400hPa
|
133
|
+
relativehumidity_300hPa
|
134
|
+
relativehumidity_250hPa
|
135
|
+
relativehumidity_200hPa
|
136
|
+
relativehumidity_150hPa
|
137
|
+
relativehumidity_100hPa
|
138
|
+
relativehumidity_70hPa
|
139
|
+
relativehumidity_50hPa
|
140
|
+
relativehumidity_30hPa
|
141
|
+
].freeze
|
142
|
+
|
143
|
+
HOURLY_VARIABLES_PRESSURE_LEVEL_CLOUDCOVER = %i[
|
144
|
+
cloudcover_1000hPa
|
145
|
+
cloudcover_975hPa
|
146
|
+
cloudcover_950hPa
|
147
|
+
cloudcover_925hPa
|
148
|
+
cloudcover_900hPa
|
149
|
+
cloudcover_850hPa
|
150
|
+
cloudcover_800hPa
|
151
|
+
cloudcover_700hPa
|
152
|
+
cloudcover_600hPa
|
153
|
+
cloudcover_500hPa
|
154
|
+
cloudcover_400hPa
|
155
|
+
cloudcover_300hPa
|
156
|
+
cloudcover_250hPa
|
157
|
+
cloudcover_200hPa
|
158
|
+
cloudcover_150hPa
|
159
|
+
cloudcover_100hPa
|
160
|
+
cloudcover_70hPa
|
161
|
+
cloudcover_50hPa
|
162
|
+
cloudcover_30hPa
|
163
|
+
].freeze
|
164
|
+
|
165
|
+
HOURLY_VARIABLES_PRESSURE_LEVEL_WIND_SPEED = %i[
|
166
|
+
windspeed_1000hPa
|
167
|
+
windspeed_975hPa
|
168
|
+
windspeed_950hPa
|
169
|
+
windspeed_925hPa
|
170
|
+
windspeed_900hPa
|
171
|
+
windspeed_850hPa
|
172
|
+
windspeed_800hPa
|
173
|
+
windspeed_700hPa
|
174
|
+
windspeed_600hPa
|
175
|
+
windspeed_500hPa
|
176
|
+
windspeed_400hPa
|
177
|
+
windspeed_300hPa
|
178
|
+
windspeed_250hPa
|
179
|
+
windspeed_200hPa
|
180
|
+
windspeed_150hPa
|
181
|
+
windspeed_100hPa
|
182
|
+
windspeed_70hPa
|
183
|
+
windspeed_50hPa
|
184
|
+
windspeed_30hPa
|
185
|
+
].freeze
|
186
|
+
|
187
|
+
HOURLY_VARIABLES_PRESSURE_LEVEL_WIND_DIRECTION = %i[
|
188
|
+
winddirection_1000hPa
|
189
|
+
winddirection_975hPa
|
190
|
+
winddirection_950hPa
|
191
|
+
winddirection_925hPa
|
192
|
+
winddirection_900hPa
|
193
|
+
winddirection_850hPa
|
194
|
+
winddirection_800hPa
|
195
|
+
winddirection_700hPa
|
196
|
+
winddirection_600hPa
|
197
|
+
winddirection_500hPa
|
198
|
+
winddirection_400hPa
|
199
|
+
winddirection_300hPa
|
200
|
+
winddirection_250hPa
|
201
|
+
winddirection_200hPa
|
202
|
+
winddirection_150hPa
|
203
|
+
winddirection_100hPa
|
204
|
+
winddirection_70hPa
|
205
|
+
winddirection_50hPa
|
206
|
+
winddirection_30hPa
|
207
|
+
].freeze
|
208
|
+
|
209
|
+
HOURLY_VARIABLES_PRESSURE_LEVEL_GEOPOTENTIAL_HEIGHT = %i[
|
210
|
+
geopotential_height_1000hPa
|
211
|
+
geopotential_height_975hPa
|
212
|
+
geopotential_height_950hPa
|
213
|
+
geopotential_height_925hPa
|
214
|
+
geopotential_height_900hPa
|
215
|
+
geopotential_height_850hPa
|
216
|
+
geopotential_height_800hPa
|
217
|
+
geopotential_height_700hPa
|
218
|
+
geopotential_height_600hPa
|
219
|
+
geopotential_height_500hPa
|
220
|
+
geopotential_height_400hPa
|
221
|
+
geopotential_height_300hPa
|
222
|
+
geopotential_height_250hPa
|
223
|
+
geopotential_height_200hPa
|
224
|
+
geopotential_height_150hPa
|
225
|
+
geopotential_height_100hPa
|
226
|
+
geopotential_height_70hPa
|
227
|
+
geopotential_height_50hPa
|
228
|
+
geopotential_height_30hPa
|
229
|
+
].freeze
|
230
|
+
|
231
|
+
HOURLY_VARIABLES = [
|
232
|
+
*HOURLY_VARIABLES_BASE,
|
233
|
+
*HOURLY_VARIABLES_ADDITIONAL,
|
234
|
+
*HOURLY_VARIABLES_SOLAR_RADIATION,
|
235
|
+
*HOURLY_VARIABLES_PRESSURE_LEVEL_TEMPERATURE,
|
236
|
+
*HOURLY_VARIABLES_PRESSURE_LEVEL_RELATIVE_HUMIDITY,
|
237
|
+
*HOURLY_VARIABLES_PRESSURE_LEVEL_CLOUDCOVER,
|
238
|
+
*HOURLY_VARIABLES_PRESSURE_LEVEL_WIND_SPEED,
|
239
|
+
*HOURLY_VARIABLES_PRESSURE_LEVEL_WIND_DIRECTION,
|
240
|
+
*HOURLY_VARIABLES_PRESSURE_LEVEL_GEOPOTENTIAL_HEIGHT,
|
241
|
+
].freeze
|
242
|
+
|
243
|
+
DAILY_VARIABLES = %i[
|
244
|
+
apparent_temperature_max
|
245
|
+
apparent_temperature_min
|
246
|
+
et0_fao_evapotranspiration
|
247
|
+
precipitation_hours
|
248
|
+
precipitation_probability_max
|
249
|
+
precipitation_sum
|
250
|
+
rain_sum
|
251
|
+
shortwave_radiation_sum
|
252
|
+
showers_sum
|
253
|
+
snowfall_sum
|
254
|
+
sunrise
|
255
|
+
sunset
|
256
|
+
temperature_2m_max
|
257
|
+
temperature_2m_min
|
258
|
+
uv_index_clear_sky_max
|
259
|
+
uv_index_max
|
260
|
+
weathercode
|
261
|
+
winddirection_10m_dominant
|
262
|
+
windgusts_10m_max
|
263
|
+
windspeed_10m_max
|
264
|
+
].freeze
|
265
|
+
|
266
|
+
rule(:current).each do
|
267
|
+
unless CURRENT_VARIABALES.include?(value)
|
268
|
+
key.failure("must be one of #{CURRENT_VARIABALES.join(", ")}")
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
rule(:hourly).each do
|
273
|
+
unless HOURLY_VARIABLES.include?(value)
|
274
|
+
key.failure("must be one of #{HOURLY_VARIABLES.join(", ")}")
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
rule(:daily).each do
|
279
|
+
unless DAILY_VARIABLES.include?(value)
|
280
|
+
key.failure("must be one of #{DAILY_VARIABLES.join(", ")}")
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
data/lib/open_meteo/forecast.rb
CHANGED
@@ -9,6 +9,8 @@ module OpenMeteo
|
|
9
9
|
class Forecast
|
10
10
|
class ForecastModelNotImplemented < StandardError
|
11
11
|
end
|
12
|
+
class WrongLocationType < StandardError
|
13
|
+
end
|
12
14
|
|
13
15
|
def initialize(client: OpenMeteo::Client.new)
|
14
16
|
@client = client
|
@@ -19,10 +21,10 @@ module OpenMeteo
|
|
19
21
|
|
20
22
|
model_definition = get_model_definition(model)
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
+
variables_object = model_definition[:variables_class].new(**variables)
|
25
|
+
variables_object.validate!
|
24
26
|
|
25
|
-
get_forecast(model_definition[:endpoint], location,
|
27
|
+
get_forecast(model_definition[:endpoint], location, variables_object)
|
26
28
|
end
|
27
29
|
|
28
30
|
private
|
@@ -42,7 +44,7 @@ module OpenMeteo
|
|
42
44
|
end
|
43
45
|
|
44
46
|
def ensure_valid_location(location)
|
45
|
-
raise
|
47
|
+
raise WrongLocationType unless location.is_a? OpenMeteo::Location
|
46
48
|
|
47
49
|
location.validate!
|
48
50
|
end
|
data/lib/open_meteo/location.rb
CHANGED
@@ -1,17 +1,14 @@
|
|
1
|
+
require_relative "location_contract"
|
2
|
+
|
1
3
|
module OpenMeteo
|
2
4
|
##
|
3
5
|
# A location for a request to OpenMeteo.
|
4
|
-
class Location
|
5
|
-
|
6
|
-
|
7
|
-
def initialize(longitude:, latitude:)
|
8
|
-
@longitude = longitude
|
9
|
-
@latitude = latitude
|
10
|
-
end
|
6
|
+
class Location < Dry::Struct
|
7
|
+
attribute :latitude, OpenMeteo::Types::Strict::Float
|
8
|
+
attribute :longitude, OpenMeteo::Types::Strict::Float
|
11
9
|
|
12
10
|
def validate!
|
13
|
-
|
14
|
-
true
|
11
|
+
LocationContract.validate!(to_hash)
|
15
12
|
end
|
16
13
|
|
17
14
|
def to_get_params
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module OpenMeteo
|
2
|
+
##
|
3
|
+
# Validation contract for OpenMeteo::Location.
|
4
|
+
class LocationContract < OpenMeteo::ApplicationContract
|
5
|
+
params do
|
6
|
+
required(:latitude).filled(:float)
|
7
|
+
required(:longitude).filled(:float)
|
8
|
+
end
|
9
|
+
|
10
|
+
rule(:latitude) { key.failure("must be within [-90;90]") if value < -90 || value > 90 }
|
11
|
+
|
12
|
+
rule(:longitude) { key.failure("must be within [-180;180]") if value < -180 || value > 180 }
|
13
|
+
end
|
14
|
+
end
|
data/lib/open_meteo/version.rb
CHANGED
data/lib/open_meteo.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: open-meteo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter Morgenstern
|
@@ -10,6 +10,46 @@ bindir: bin
|
|
10
10
|
cert_chain: []
|
11
11
|
date: 2023-11-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: dry-struct
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.0.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.0'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.0.0
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: dry-validation
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.0'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 1.0.0
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '1.0'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 1.0.0
|
13
53
|
- !ruby/object:Gem::Dependency
|
14
54
|
name: faraday
|
15
55
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,12 +78,16 @@ extensions: []
|
|
38
78
|
extra_rdoc_files: []
|
39
79
|
files:
|
40
80
|
- lib/open_meteo.rb
|
81
|
+
- lib/open_meteo/application_contract.rb
|
41
82
|
- lib/open_meteo/client.rb
|
42
83
|
- lib/open_meteo/client/config.rb
|
43
84
|
- lib/open_meteo/client/url_builder.rb
|
44
85
|
- lib/open_meteo/forecast.rb
|
45
86
|
- lib/open_meteo/forecast/variables/general.rb
|
87
|
+
- lib/open_meteo/forecast/variables/general_contract.rb
|
46
88
|
- lib/open_meteo/location.rb
|
89
|
+
- lib/open_meteo/location_contract.rb
|
90
|
+
- lib/open_meteo/types.rb
|
47
91
|
- lib/open_meteo/version.rb
|
48
92
|
homepage: https://github.com/open-meteo-ruby/open-meteo-ruby
|
49
93
|
licenses:
|
@@ -65,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
65
109
|
- !ruby/object:Gem::Version
|
66
110
|
version: '0'
|
67
111
|
requirements: []
|
68
|
-
rubygems_version: 3.3.
|
112
|
+
rubygems_version: 3.3.26
|
69
113
|
signing_key:
|
70
114
|
specification_version: 4
|
71
115
|
summary: A client for OpenMeteo weather data
|