tlaw 0.0.1

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.
@@ -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