tlaw 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
1
+ require 'pp'
2
+
3
+ $:.unshift 'lib'
4
+ require 'tlaw'
5
+
6
+ begin
7
+ require 'dotenv'
8
+ Dotenv.load
9
+ rescue LoadError
10
+ end
@@ -0,0 +1,113 @@
1
+ module TLAW
2
+ module Examples
3
+ class ForecastIO < TLAW::API
4
+ define do
5
+ base 'https://api.forecast.io/forecast/{api_key}'
6
+
7
+ desc %Q{
8
+ The Forecast API allows you to look up the weather anywhere on
9
+ the globe, returning (where available):
10
+
11
+ * Current conditions
12
+ * Minute-by-minute forecasts out to 1 hour
13
+ * Hour-by-hour forecasts out to 48 hours
14
+ * Day-by-day forecasts out to 7 days
15
+ }
16
+
17
+ docs 'https://developer.forecast.io/docs/v2'
18
+
19
+ param :api_key, required: true,
20
+ desc: 'Register at https://developer.forecast.io/register to obtain it'
21
+ param :units, enum: %i[us si ca uk2 auto], default: :us,
22
+ desc: %Q{
23
+ Response units. Values:
24
+ * `:us` is default;
25
+ * `:si` is meters/celsius/hectopascals;
26
+ * `:ca` is identical to si, except that `windSpeed` is in
27
+ kilometers per hour.
28
+ * `:uk2` is identical to si, except that `windSpeed` is in
29
+ miles per hour, and `nearestStormDistance` and `visibility`
30
+ are in miles, as in the US.
31
+ * `auto` selects the relevant units automatically, based
32
+ on geographic location.
33
+ }
34
+
35
+ param :lang, default: :en,
36
+ desc: %Q{
37
+ Return summary properties in the desired language. (2-letters code)
38
+ }
39
+
40
+ endpoint :forecast, '/{lat},{lng}' do
41
+ desc %Q{Forecast for the next week.}
42
+
43
+ docs 'https://developer.forecast.io/docs/v2#forecast_call'
44
+
45
+ param :lat, :to_f, required: true, desc: 'Latitude'
46
+ param :lng, :to_f, required: true, desc: 'Longitude'
47
+
48
+ param :exclude, Array,
49
+ desc: %Q{
50
+ Exclude some number of data blocks from the API response.
51
+ This is useful for reducing latency and saving cache space.
52
+ Should be a list (without spaces) of any of the following:
53
+ currently, minutely, hourly, daily, alerts, flags.
54
+ }
55
+
56
+ param :extended_hourly, field: :extend,
57
+ enum: {false => nil, true => 'hourly'},
58
+ desc: %Q{
59
+ When present, return hourly data for the next seven days,
60
+ rather than the next two.
61
+ }
62
+ end
63
+
64
+ endpoint :time_machine, '/{lat},{lng},{at}' do
65
+ desc %Q{
66
+ Observed weather at a given time (for many places, up to 60
67
+ years in the past).
68
+
69
+ For future dates, returns numerical forecast for the next week
70
+ and seasonal averages beyond that.
71
+ }
72
+
73
+ docs 'https://developer.forecast.io/docs/v2#time_call'
74
+
75
+ param :lat, :to_f, required: true, desc: 'Latitude'
76
+ param :lng, :to_f, required: true, desc: 'Longitude'
77
+ param :at, :to_time, format: :to_i, required: true,
78
+ desc: 'Date in past or future.'
79
+
80
+ param :exclude, Array,
81
+ desc: %Q{
82
+ Exclude some number of data blocks from the API response.
83
+ This is useful for reducing latency and saving cache space.
84
+ Should be a list (without spaces) of any of the following:
85
+ currently, minutely, hourly, daily, alerts, flags.
86
+ }
87
+ end
88
+
89
+ post_process 'currently.time', &Time.method(:at)
90
+
91
+ post_process_items('minutely.data') {
92
+ post_process 'time', &Time.method(:at)
93
+ }
94
+
95
+ post_process_items('hourly.data') {
96
+ post_process 'time', &Time.method(:at)
97
+ }
98
+
99
+ post_process_items('daily.data') {
100
+ post_process 'time', &Time.method(:at)
101
+ post_process 'sunriseTime', &Time.method(:at)
102
+ post_process 'sunsetTime', &Time.method(:at)
103
+ }
104
+ end
105
+ end
106
+
107
+ # TODO: X-Forecast-API-Calls header is useful!
108
+ # TODO: The Forecast Data API supports HTTP compression.
109
+ # We heartily recommend using it, as it will make responses much
110
+ # smaller over the wire. To enable it, simply add an
111
+ # Accept-Encoding: gzip header to your request.
112
+ end
113
+ end
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative 'demo_base'
3
+ require_relative 'forecast_io'
4
+
5
+ # This wrapper demo demonstrates that even a simple (one call, several
6
+ # params) API could benefit from TLAW by param types checking &
7
+ # sky-level discoverability.
8
+
9
+ p TLAW::Examples::ForecastIO
10
+ # #<TLAW::Examples::ForecastIO: call-sequence: TLAW::Examples::ForecastIO.new(api_key:, units: :us, lang: :en); endpoints: forecast, time_machine; docs: .describe>
11
+
12
+ p TLAW::Examples::ForecastIO.describe
13
+ # TLAW::Examples::ForecastIO.new(api_key:, units: :us, lang: :en)
14
+ # The Forecast API allows you to look up the weather anywhere on
15
+ # the globe, returning (where available):
16
+ #
17
+ # * Current conditions
18
+ # * Minute-by-minute forecasts out to 1 hour
19
+ # * Hour-by-hour forecasts out to 48 hours
20
+ # * Day-by-day forecasts out to 7 days
21
+ #
22
+ # Docs: https://developer.forecast.io/docs/v2
23
+ #
24
+ # @param api_key Register at https://developer.forecast.io/register to obtain it
25
+ # @param units
26
+ # Response units. Values:
27
+ # * `:us` is default;
28
+ # * `:si` is meters/celsius/hectopascals;
29
+ # * `:ca` is identical to si, except that `windSpeed` is in
30
+ # kilometers per hour.
31
+ # * `:uk2` is identical to si, except that `windSpeed` is in
32
+ # miles per hour, and `nearestStormDistance` and `visibility`
33
+ # are in miles, as in the US.
34
+ # * `auto` selects the relevant units automatically, based
35
+ # on geographic location.
36
+ #
37
+ # Possible values: :us, :si, :ca, :uk2, :auto (default = :us)
38
+ # @param lang
39
+ # Return summary properties in the desired language. (2-letters code)
40
+ # (default = :en)
41
+ #
42
+ # Endpoints:
43
+ #
44
+ # .forecast(lat, lng, exclude: nil, extended_hourly: nil)
45
+ # Forecast for the next week.
46
+ #
47
+ # .time_machine(lat, lng, at, exclude: nil)
48
+ # Observed weather at a given time (for many places, up to 60
49
+ # years in the past).
50
+
51
+ # You need to create key here: https://developer.forecast.io/register
52
+ # And run the script this way:
53
+ #
54
+ # FORECAST_IO={your_id} examples/forecast_io_demo.rb
55
+ #
56
+
57
+ weather = TLAW::Examples::ForecastIO
58
+ .new(api_key: ENV['FORECAST_IO'], units: :si)
59
+
60
+ res = weather.forecast(40.7127, -74.0059, extended_hourly: true)
61
+ pp res['minutely.data'].first
62
+ # {"time"=>2016-09-12 21:20:00 +0300,
63
+ # "precipIntensity"=>0,
64
+ # "precipProbability"=>0}
65
+
66
+ res = weather.time_machine(49.999892, 36.242392, Date.parse('2020-02-01'))
67
+ pp res['daily.data'].columns('time', 'temperatureMin', 'temperatureMax', 'dewPoint', 'moonPhase').first
68
+ # {"time"=>2020-02-01 00:00:00 +0200,
69
+ # "temperatureMin"=>-6.61,
70
+ # "temperatureMax"=>-4.37,
71
+ # "dewPoint"=>-7.7,
72
+ # "moonPhase"=>0.23}
@@ -0,0 +1,266 @@
1
+ require 'geo/coord'
2
+
3
+ module TLAW
4
+ module Examples
5
+ class OpenWeatherMap < TLAW::API
6
+ define do
7
+ desc %Q{
8
+ API for [OpenWeatherMap](http://openweathermap.org/). Only parts
9
+ available for free are implemented (as only them could be tested).
10
+ }
11
+
12
+ docs 'http://openweathermap.org/api'
13
+
14
+ base 'http://api.openweathermap.org/data/2.5'
15
+
16
+ param :appid, required: true,
17
+ desc: 'You need to receive it at http://openweathermap.org/appid (free)'
18
+
19
+ param :lang, default: 'en',
20
+ desc: %Q{Language of API responses (affects weather description only).
21
+ See http://openweathermap.org/current#multi for list of supported languages.}
22
+
23
+ param :units, enum: %i[standard metric imperial], default: :standard,
24
+ desc: 'Units for temperature and other values. Standard is Kelvin.'
25
+
26
+ namespace :current, '/weather' do
27
+ desc %Q{
28
+ Allows to obtain current weather at one place, designated
29
+ by city, location or zip code.
30
+ }
31
+
32
+ docs 'http://openweathermap.org/current'
33
+
34
+ endpoint :city, '?q={city}{,country_code}' do
35
+ desc %Q{
36
+ Current weather by city name (with optional country code
37
+ specification).
38
+ }
39
+
40
+ docs 'http://openweathermap.org/current#name'
41
+
42
+ param :city, required: true, desc: 'City name'
43
+ param :country_code, desc: 'ISO 3166 2-letter country code'
44
+ end
45
+
46
+ endpoint :city_id, '?id={city_id}' do
47
+ desc %Q{
48
+ Current weather by city id. Recommended by OpenWeatherMap
49
+ docs.
50
+
51
+ List of city ID city.list.json.gz can be downloaded at
52
+ http://bulk.openweathermap.org/sample/
53
+ }
54
+
55
+ docs 'http://openweathermap.org/current#cityid'
56
+
57
+ param :city_id, required: true, desc: 'City ID (as defined by OpenWeatherMap)'
58
+ end
59
+
60
+ endpoint :location, '?lat={lat}&lon={lng}' do
61
+ desc %Q{
62
+ Current weather by geographic coordinates.
63
+ }
64
+
65
+ docs 'http://openweathermap.org/current#geo'
66
+
67
+ param :lat, :to_f, required: true, desc: 'Latitude'
68
+ param :lng, :to_f, required: true, desc: 'Longitude'
69
+ end
70
+
71
+ endpoint :zip, '?zip={zip}{,country_code}' do
72
+ desc %Q{
73
+ Current weather by ZIP code (with optional country code
74
+ specification).
75
+ }
76
+
77
+ docs 'http://openweathermap.org/current#zip'
78
+
79
+ param :zip, required: true, desc: 'ZIP code'
80
+ param :country_code, desc: 'ISO 3166 2-letter country code'
81
+ end
82
+
83
+ endpoint :group, '/../group?id={city_ids}' do
84
+ desc %Q{
85
+ Current weather in several cities by their ids.
86
+
87
+ List of city ID city.list.json.gz can be downloaded at
88
+ http://bulk.openweathermap.org/sample/
89
+ }
90
+
91
+ docs 'http://openweathermap.org/current#cities'
92
+
93
+ param :city_ids, :to_a, required: true
94
+ end
95
+ end
96
+
97
+ namespace :find do
98
+ desc %Q{
99
+ Allows to find some place (and weather in it) by set of input
100
+ parameters.
101
+ }
102
+
103
+ docs 'http://openweathermap.org/current#accuracy'
104
+
105
+ endpoint :by_name, '?q={start_with}{,country_code}' do
106
+ desc %Q{
107
+ Looks for cities by beginning of their names.
108
+ }
109
+
110
+ docs 'http://openweathermap.org/current#accuracy'
111
+
112
+ param :start_with, required: true, desc: 'Beginning of city name'
113
+ param :country_code, desc: 'ISO 3166 2-letter country code'
114
+
115
+ param :cnt, :to_i, range: 1..50, default: 10,
116
+ desc: 'Max number of results to return'
117
+
118
+ param :accurate, field: :type,
119
+ enum: {true => 'accurate', false => 'like'},
120
+ default: true,
121
+ desc: %Q{Accuracy level of result.
122
+ true returns exact match values (accurate).
123
+ false returns results by searching for that substring (like).
124
+ }
125
+ end
126
+
127
+ endpoint :around, '?lat={lat}&lon={lng}' do
128
+ desc %Q{
129
+ Looks for cities around geographical coordinates.
130
+ }
131
+
132
+ docs 'http://openweathermap.org/current#cycle'
133
+
134
+ param :lat, :to_f, required: true, desc: 'Latitude'
135
+ param :lng, :to_f, required: true, desc: 'Longitude'
136
+
137
+ param :cnt, :to_i, range: 1..50, default: 10,
138
+ desc: 'Max number of results to return'
139
+
140
+ param :cluster, enum: {true => 'yes', false: 'no'},
141
+ default: true,
142
+ desc: 'Use server clustering of points'
143
+ end
144
+
145
+ # Real path is api/bbox/city - not inside /find, but logically
146
+ # we want to place it here
147
+ endpoint :inside, '/../box/city?bbox={lng_left},{lat_bottom},{lng_right},{lat_top},{zoom}' do
148
+ desc %Q{
149
+ Looks for cities inside specified rectangle zone.
150
+ }
151
+
152
+ docs 'http://openweathermap.org/current#rectangle'
153
+
154
+ param :lat_top, :to_f, required: true, keyword: true
155
+ param :lat_bottom, :to_f, required: true, keyword: true
156
+ param :lng_left, :to_f, required: true, keyword: true
157
+ param :lng_right, :to_f, required: true, keyword: true
158
+ param :zoom, :to_i, default: 10, keyword: true,
159
+ desc: 'Map zoom level.'
160
+
161
+ param :cluster, enum: {true => 'yes', false: 'no'},
162
+ default: true,
163
+ desc: 'Use server clustering of points'
164
+ end
165
+ end
166
+
167
+ # http://openweathermap.org/forecast5
168
+ namespace :forecast do
169
+ desc %Q{
170
+ Allows to obtain weather forecast for 5 days with 3-hour
171
+ frequency.
172
+
173
+ NB: OpenWeatherMap also implement [16-day forecast](http://openweathermap.org/forecast16),
174
+ but it have no free option and can not be tested. That's why
175
+ we don't implement it.
176
+ }
177
+
178
+ docs 'http://openweathermap.org/forecast5'
179
+
180
+ endpoint :city, '?q={city}{,country_code}' do
181
+ desc %Q{
182
+ Weather forecast by city name (with optional country code
183
+ specification).
184
+ }
185
+
186
+ docs 'http://openweathermap.org/forecast5#name5'
187
+
188
+ param :city, required: true, desc: 'City name'
189
+ param :country_code, desc: 'ISO 3166 2-letter country code'
190
+ end
191
+
192
+ endpoint :city_id, '?id={city_id}' do
193
+ desc %Q{
194
+ Current weather by city id. Recommended by OpenWeatherMap
195
+ docs.
196
+
197
+ List of city ID city.list.json.gz can be downloaded at
198
+ http://bulk.openweathermap.org/sample/
199
+ }
200
+
201
+ docs 'http://openweathermap.org/forecast5#cityid5'
202
+
203
+ param :city_id, required: true, desc: 'City ID (as defined by OpenWeatherMap)'
204
+ end
205
+
206
+ endpoint :location, '?lat={lat}&lon={lng}' do
207
+ desc %Q{
208
+ Weather forecast by geographic coordinates.
209
+ }
210
+
211
+ docs 'http://openweathermap.org/forecast5#geo5'
212
+
213
+ param :lat, :to_f, required: true, desc: 'Latitude'
214
+ param :lng, :to_f, required: true, desc: 'Longitude'
215
+ end
216
+
217
+ post_process { |e|
218
+ e['city.coord'] = Geo::Coord.new(e['city.coord.lat'], e['city.coord.lon']) \
219
+ if e['city.coord.lat'] && e['city.coord.lon']
220
+ }
221
+ post_process('city.coord.lat') { nil }
222
+ post_process('city.coord.lon') { nil }
223
+ end
224
+
225
+ # OpenWeatherMap reports most of logical errors with HTTP code
226
+ # 200 and responses like {cod: "500", message: "Error message"}
227
+ post_process { |h|
228
+ !h.key?('cod') || (200..400).cover?(h['cod'].to_i) or
229
+ fail "#{h['cod']}: #{h['message']}"
230
+ }
231
+
232
+ WEATHER_POST_PROCESSOR = lambda do |*|
233
+ # Most of the time there is exactly one weather item...
234
+ # ...but sometimes there are two. So, flatterning them looks
235
+ # more reasonable than having DataTable of 1-2 rows.
236
+ post_process { |h|
237
+ h['weather2'] = h['weather'].last if h['weather'] && h['weather'].count > 1
238
+ }
239
+ post_process('weather', &:first)
240
+
241
+ post_process('dt', &Time.method(:at))
242
+ post_process('dt_txt') { nil } # TODO: we need cleaner way to say "remove this"
243
+ post_process('sys.sunrise', &Time.method(:at))
244
+ post_process('sys.sunset', &Time.method(:at))
245
+
246
+ # https://github.com/zverok/geo_coord promo here!
247
+ post_process { |e|
248
+ e['coord'] = Geo::Coord.new(e['coord.lat'], e['coord.lon']) if e['coord.lat'] && e['coord.lon']
249
+ }
250
+ post_process('coord.lat') { nil }
251
+ post_process('coord.lon') { nil }
252
+
253
+ # See http://openweathermap.org/weather-conditions#How-to-get-icon-URL
254
+ post_process('weather.icon') { |i| "http://openweathermap.org/img/w/#{i}.png" }
255
+ end
256
+
257
+ # For endpoints returning weather in one place
258
+ instance_eval(&WEATHER_POST_PROCESSOR)
259
+
260
+ # For endpoints returning list of weathers (forecast or several
261
+ # cities).
262
+ post_process_items('list', &WEATHER_POST_PROCESSOR)
263
+ end
264
+ end
265
+ end
266
+ end