tlaw 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6d841ff577a627eeecb9897b2cf4a20778eef17c
4
+ data.tar.gz: f52b898df817a0982b98d05000dc71f1e35cabb4
5
+ SHA512:
6
+ metadata.gz: bbcbe2d3adbb328867b018d5664e2027191be9c47763ce8295bad2391a0ba9011af89fa073d920d016dc0f0ddf7f9c3fe60e1436458a88596658a6de93f6d28f
7
+ data.tar.gz: 63e85a71614104dff15bde2f9e188a16c8e72aadf3c037eeae06699e7ec9af612be73a2d5c1250fabccc31f8f1d159aa8f6ae1297cea13a04910c51868deb586
@@ -0,0 +1,11 @@
1
+ engines:
2
+ rubocop:
3
+ enabled: true
4
+ duplication:
5
+ enabled: true
6
+ config:
7
+ languages:
8
+ - ruby
9
+
10
+ exclude_paths:
11
+ - "examples/"
@@ -0,0 +1,2 @@
1
+ --markup=markdown
2
+ --no-private
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Victor 'zverok' Shepelev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,438 @@
1
+ # TLAW - The Last API Wrapper
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/tlaw.svg)](http://badge.fury.io/rb/tlaw)
4
+ [![Build Status](https://travis-ci.org/molybdenum-99/tlaw.svg?branch=master)](https://travis-ci.org/molybdenum-99/tlaw)
5
+ [![Coverage Status](https://coveralls.io/repos/molybdenum-99/tlaw/badge.svg?branch=master)](https://coveralls.io/r/molybdenum-99/tlaw?branch=master)
6
+
7
+ **TLAW** (pronounce it like "tea+love"... or whatever) is the last (and
8
+ only) API wrapper framework for _get-only APIes_<sup>[*](#get-only-api)</sup>
9
+ (think weather, search, economical indicators, geonames and so on).
10
+
11
+ ## Table Of Contents
12
+
13
+ * [Features](#features)
14
+ * [Why TLAW?](#why-tlaw)
15
+ * [Usage](#usage)
16
+ * [URLs and params description](#urls-and-params-description)
17
+ * [Response processing](#response-processing)
18
+ * [Flat hashes](#flat-hashes)
19
+ * [DataTable](#datatable)
20
+ * [Post-processing](#post-processing)
21
+ * [All at once](#all-at-once)
22
+ * [Documentability](#documentability)
23
+ * [Some demos](#some-demos)
24
+ * [Installation & compatibility](#installation-&-compatibility)
25
+ * [Upcoming features](#upcoming-features)
26
+ * [Get-only API](#get-only-api)
27
+ * [Current status](#current-status)
28
+ * [Links](#links)
29
+ * [Author](#author)
30
+ * [License](#license)
31
+
32
+ ## Features
33
+
34
+ * **Pragmatic**: thorougly designed with tens of real world examples in mind,
35
+ not just your textbook's "perfect orthogonal REST API";
36
+ * **Opinionated**: goal is clean and logical Ruby libary, not just mechanical
37
+ 1-by-1 endpoint-per-method wrapper for every fancy hivemind invention;
38
+ * **Easy and readable** definitions;
39
+ * **Discoverable**: once API defined in TLAW terms, you can easily investigate
40
+ it in runtime, obtain meaningful errors like "param `foo` is missing
41
+ while trying to access endpoint `bar`" and so on;
42
+ * **Sane metaprogramming**: allows to define entire branchy API wrapper with
43
+ tons of pathes and endpoints in really concise manner, while creating
44
+ _all_ corresponding classes/methods at definition time: so, at runtime
45
+ you have no 20-level dynamic dispatching, just your usual method calls
46
+ with clearly defined arguments and compact backtraces.
47
+
48
+ Take a look at our "model" OpenWeatherMap [wrapper](https://github.com/molybdenum-99/tlaw/blob/master/examples/open_weather_map.rb)
49
+ and [demo](https://github.com/molybdenum-99/tlaw/blob/master/examples/open_weather_map_demo.rb)
50
+ of its usage, showing how all those things work in reality.
51
+
52
+ ## Why TLAW?
53
+
54
+ There are ton of small (and not-so-small) useful APIs about world around:
55
+ weather, movies, geographical features, dictionaries, world countries
56
+ statistics... Typically, when trying to use one of them from Ruby (or,
57
+ to be honest, from any programming language), you are stuck with two
58
+ options:
59
+
60
+ 1. Study and use (or invent and build) some custom hand-made Wrapper
61
+ Library™ with ton of very custom design decisions (should responses
62
+ be just hashes, or [Hashie](https://github.com/intridea/hashie), or
63
+ real classes for each kind of response? What are the inputs? Where should
64
+ api key go, to global param?); or
65
+ 2. Just "go commando" (sorry for the bad pun): construct URLs yourself,
66
+ parse responses yourself, control params (or ignore the control) yourself.
67
+
68
+ TLAW tries to close this gap: provide a base for _breath-easy_ API description
69
+ which produces solid, fast and reliable wrappers.
70
+
71
+ ## Usage
72
+
73
+ ### URLs and params description
74
+
75
+ ```ruby
76
+ class Example < TLAW::API
77
+ base 'http://api.example.com'
78
+
79
+ param :api_key, required: true # this would be necessary for API instance creation
80
+ # So, the API instance would be e = Example.new(api_key: '123')
81
+ # ...and parameter ?api_key=123 would be added to any request
82
+
83
+ endpoint :foo # The simplest endpoint, will query "http://api.example.com/foo"
84
+ # And then you just do e.foo and obtain result
85
+
86
+ endpoint :bar, '/baz.json' # Path to query rewritten, will query "http://api.example.com/baz.json"
87
+ # Method is still e.bar, though.
88
+
89
+ # Now, for params definition:
90
+ endpont :movie do
91
+ param :id
92
+ end
93
+ # Method call would be movie(id: '123')
94
+ # Generated URL would be "http://api.example.com/movie?id=123"
95
+
96
+ # When param is part of the path, you can use RFC 6570
97
+ # URL template standard:
98
+ endpoint :movie, '/movies/{id}'
99
+ # That would generate method which is called like movie('123')
100
+ # ...and call to "http://api.example.com/movies/123"
101
+
102
+ # Now, we can stack endpoints in namespaces
103
+ namespace :foo do # adds /foo to path
104
+ namespace :bar, '/baz' do # optional path parameter works
105
+ endpoint :blah # URL for call would be "http://api.example.com/foo/baz/blah"
106
+ # And method call would be like e.foo.bar.blah(parameters)
107
+ end
108
+
109
+ # URL normalization works, so you can stack in namespaces even
110
+ # things not related to them in source API, "redesigning" API on
111
+ # the fly.
112
+ endpoint :books, '/../books.json' # Real URL would be "http://api.example.com/books"
113
+ # Yet method call is still namespaced like e.foo.books
114
+ end
115
+
116
+ # Namespaces can have their own input parameters
117
+ namespace :foo, '/foo/{id}' do
118
+ endpoint :bar # URL would be "http://api.example.com/foo/123/bar
119
+ # method call would be e.foo(123).bar
120
+ end
121
+
122
+ # ...and everything works in all possible and useful ways, just check
123
+ # docs and demos.
124
+ end
125
+ ```
126
+
127
+ See [DSL module docs](http://www.rubydoc.info/gems/tlaw/TLAW/DSL) for
128
+ full description of all features (there are few, yet very powerful).
129
+
130
+ ### Response processing
131
+
132
+ TLAW is really opinionated about response processing. Main things:
133
+
134
+ 1. [Hashes are "flattened"](#flat-hashes);
135
+ 2. [Arrays of hashes are converted to `DataTable`s](#datatable);
136
+ 3. [Post-processors for fields are easily defined](#post-processing)
137
+
138
+ #### Flat hashes
139
+
140
+ The main (and usually top-level) answer of (JSON) API is a Hash/dictionary.
141
+ TLAW takes all multilevel hashes and make them flat.
142
+
143
+ Here is an example.
144
+
145
+ Source API responds like:
146
+
147
+ ```json
148
+ {
149
+ "meta": {
150
+ "code": "OK",
151
+ },
152
+ "weahter": {
153
+ "temp": 10,
154
+ "precipation": 138
155
+ },
156
+ "location": {
157
+ "lat": 123,
158
+ "lon": 456
159
+ }
160
+ ...
161
+ }
162
+ ```
163
+
164
+ But TLAW response to `api.endpoint(params)` would return you a Hash looking
165
+ this way:
166
+
167
+ ```json
168
+ {
169
+ "meta.code": "OK",
170
+ "weahter.temp": 10,
171
+ "weahter.precipation": 138,
172
+ "location.lat": 123,
173
+ "location.lon": 456
174
+ ...
175
+ }
176
+ ```
177
+
178
+ Reason? If you think of it and experiment with several examples, typically
179
+ with new & unexplored API you'll came up with code like:
180
+
181
+ ```ruby
182
+ p response
183
+ # => 3 screens of VERY IMPORTANT RESPONSE
184
+ p response.class
185
+ # => Hash, ah, ok
186
+ p response.keys
187
+ # => ["meta", "weather", "location"], hmmm...
188
+ p response['weather']
189
+ # => stil 2.5 screens of unintelligible details
190
+ p response['weather'].class
191
+ # => Hash, ah!
192
+ p response['weather'].keys
193
+ # => and ad infinitum, real APIs are easily go 6-8 levels down
194
+ ```
195
+
196
+ Now, with "opinionated" TLAW's flattening, for _any_ API you just do
197
+ the one and final `response.keys` and that's it: you see every available
198
+ data key, deep to the deepest depth.
199
+
200
+ > NB: probably, in the next versions TLAW will return some Hash descendant,
201
+ which would also still allow you to do `response['weather']` and receive
202
+ that "slice". Or it would not :) We are experimenting!
203
+
204
+ #### DataTable
205
+
206
+ The second main type of a (JSON) API answer, or of a part of an answer
207
+ is an array of homogenous hashes, like:
208
+
209
+ * list of data points (date - weather at that date);
210
+ * list of data objects (city id - city name - latitude - longitude);
211
+ * list of views to the data (climate model - projected temperature);
212
+ * and so on.
213
+
214
+ TLAW wraps this kind of data (array of homogenous hashes, or tables with
215
+ named columns) into `DataTable` structure, which you can think of as an
216
+ Excel spreadsheet (2d array with named columns), or loose DataFrame
217
+ pattern implementation (just like [daru](https://github.com/v0dro/daru)
218
+ or [pandas](http://pandas.pydata.org/), but seriously simpler—and much
219
+ more suited to the case).
220
+
221
+ Imagine you have an API responding something like:
222
+
223
+ ```json
224
+ {
225
+ "meta": {"count": 20},
226
+ "data": [
227
+ {"date": "2016-09-01", "temp": 20, "humidity": 40},
228
+ {"date": "2016-09-02", "temp": 21, "humidity": 40},
229
+ {"date": "2016-09-03", "temp": 16, "humidity": 36},
230
+ ...
231
+ ]
232
+ }
233
+ ```
234
+
235
+ With TLAW, you'll see this response this way:
236
+
237
+ ```ruby
238
+ pp response
239
+ {"meta.count"=>20,
240
+ "data"=>#<TLAW::DataTable[date, temp, humidity] x 20>}
241
+ # ^ That's all. Small and easy to grasp what is what. 3 named columns,
242
+ # 20 similar rows.
243
+
244
+ d = response['data']
245
+ # => #<TLAW::DataTable[date, temp, humidity] x 20>
246
+
247
+ d.count # Array-alike
248
+ # => 20
249
+ d.first
250
+ # => {"date" => "2016-09-01", "temp" => 20, "humidity" => 40}
251
+
252
+ d.keys # Hash-alike
253
+ # => ["date", "temp", "humidity"]
254
+ d["date"]
255
+ # => ["2016-09-01", "2016-09-02", "2016-09-03" ...
256
+
257
+ # And stuff:
258
+ d.to_h
259
+ # => {"date" => [...], "temp" => [...] ....
260
+ d.to_a
261
+ # => [{"date" => ..., "temp" => ..., "humidity" => ...}, {"date" => ...
262
+
263
+ d.columns('date', 'temp') # column-wise slice
264
+ # => #<TLAW::DataTable[date, temp] x 20>
265
+ d.columns('date', 'temp').first # and so on
266
+ # => {"date" => "2016-09-01", "temp" => 20}
267
+ ```
268
+
269
+ Take a look at [DataTable docs](http://www.rubydoc.info/gems/tlaw/TLAW/DataTable)
270
+ and join designing it!
271
+
272
+ #### Post-processing
273
+
274
+ When you are not happy with result representation, you can post-process
275
+ them in several ways:
276
+
277
+ ```ruby
278
+ # input is entire response, block can mutate it
279
+ post_process { |hash| hash['foo'] = 'bar' }
280
+
281
+ # input is entire response, and response is fully replaced with block's
282
+ # return value
283
+ post_process { |hash| hash['foo'] } # Now only "foo"s value will be response
284
+
285
+ # input is value of response's key "some_key", return value of a block
286
+ # becames new value of "some_key".
287
+ post_process('some_key') { |val| other_val }
288
+
289
+ # Post-processing each item, if response['foo'] is array:
290
+ post_process_items('foo') {
291
+ # mutate entire item
292
+ post_process { |item| item.delete('bar') }
293
+
294
+ # if item is a Hash, replace its "bar" value
295
+ post_process('bar') { |val| val.to_s }
296
+ }
297
+
298
+ # More realistic examples:
299
+ post_process('meta.count', &:to_i)
300
+ post_process('daily') {
301
+ post_process('date', &Date.method(:parse))
302
+ }
303
+ post_process('auxiliary_value') { nil } # Nil's will be thrown away completely
304
+ ```
305
+
306
+ See full post-processing features descriptions in
307
+ [DSL module docs](http://www.rubydoc.info/gems/tlaw/TLAW/DSL).
308
+
309
+ #### All at once
310
+
311
+ All described response processing steps are performed in this order:
312
+ * parsing and initial flattening of JSON (or XML) hash;
313
+ * applying post-processors (and flatten the response after _each_ of
314
+ them);
315
+ * make `DataTable`s from arrays of hashes.
316
+
317
+ ### Documentability
318
+
319
+ You do it this way:
320
+
321
+ ```ruby
322
+ class MyAPI < TLAW::API
323
+ desc %Q{
324
+ This is API, it works.
325
+ }
326
+
327
+ docs 'http://docs.example.com'
328
+
329
+ namespace :ns do
330
+ desc %Q{
331
+ It is some interesting thing.
332
+ }
333
+
334
+ docs 'http://docs.example.com/ns'
335
+
336
+ endpoint :baz do
337
+ desc %Q{
338
+ Should be useful.
339
+ }
340
+
341
+ docs 'http://docs.example.com/ns#baz'
342
+
343
+ param :param1,
344
+ desc: %Q{
345
+ You don't need it, really.
346
+ }
347
+ end
348
+ end
349
+ end
350
+ ```
351
+
352
+ All of above is optional, but when provided, allows to investigate
353
+ things at runtime (in IRB/pry or test scripts). Again, look at
354
+ [OpenWeatherMap demo](https://github.com/molybdenum-99/tlaw/blob/master/examples/open_weather_map_demo.rb),
355
+ it shows how docs could be useful at runtime.
356
+
357
+ ## Some demos
358
+
359
+ * Full-featured API wrappers:
360
+ * OpenWeatherMap: [source API docs](http://openweathermap.org/api),
361
+ [wrapper](https://github.com/molybdenum-99/tlaw/blob/master/examples/open_weather_map.rb),
362
+ extensively commented & explained
363
+ [demo code](https://github.com/molybdenum-99/tlaw/blob/master/examples/open_weather_map_demo.rb);
364
+ * ForecastIO: [API docs](https://developer.forecast.io/docs/v2),
365
+ [wrapper](https://github.com/molybdenum-99/tlaw/blob/master/examples/forecast_io.rb),
366
+ [demo code](https://github.com/molybdenum-99/tlaw/blob/master/examples/forecast_io_demo.rb);
367
+ * Demos of "fire-and-forget" wrappers:
368
+ * Urbandictionary's small and unofficial
369
+ [API wrapper](https://github.com/molybdenum-99/tlaw/blob/master/examples/urbandictionary_demo.rb);
370
+ * [Partial wrapper](https://github.com/molybdenum-99/tlaw/blob/master/examples/tmdb_demo.rb)
371
+ only for some features of large [TMDB API](docs.themoviedb.apiary.io/).
372
+ It also shows [on-the-fly updating](https://github.com/molybdenum-99/tlaw/blob/master/examples/tmdb_demo.rb#L85)
373
+ of already existing API wrapper to add some features.
374
+
375
+ ## Installation & compatibility
376
+
377
+ Just `gem install tlaw` or add it to your `Gemfile`, nothing fancy.
378
+
379
+ Required Ruby version is 2.1+, JRuby works, Rubinius seems like not.
380
+
381
+ ## Upcoming features
382
+
383
+ _(in no particular order)_
384
+
385
+ * [ ] Expose Faraday options (backends, request headers);
386
+ * [ ] Request-headers based auth;
387
+ * [ ] Responses caching;
388
+ * [ ] Response headers processing DSL;
389
+ * [ ] Paging support;
390
+ * [ ] Frequency-limited API support (requests counting);
391
+ * [ ] YARD docs generation for resulting wrappers;
392
+ * [ ] More solid wrapper demos (weather sites, geonames, worldbank);
393
+ * [ ] Approaches to testing generated wrappers (just good ol' VCR should
394
+ work, probably);
395
+ * [ ] Splat parameters.
396
+
397
+ ## Get-only API
398
+
399
+ What is those "Get-only APIs" TLAW is suited for?
400
+
401
+ * It is only for _getting_ data, not changing them (though, API may use
402
+ HTTP POST requests in reality—for example, to transfer large request
403
+ objects);
404
+ * It would be cool if our weather APIs could allow things like
405
+ `POST /weather/kharkiv {sky: 'sunny', temp: '+21°C'}` in the middle
406
+ of December, huh? But we are not leaving in the world like this.
407
+ For now.
408
+ * It has utterly simple authentication protocol like "give us `api_key`
409
+ param in query" (though, TLAW plans to support more complex authentication);
410
+ * It typically returns JSON answers (though, TLAW supports XML via
411
+ awesome [crack](https://github.com/jnunemaker/crack)).
412
+
413
+ Alongside already mentioned examples (weather and so on), you can build
414
+ TLAW-backed "get-only" wrappers for bigger APIs (like Twitter), when
415
+ "gathering twits" is all you need. (Though, to be honest, TLAW's current
416
+ authorization abilities is far simpler than
417
+ [Twitter requirements](https://dev.twitter.com/oauth/application-only)).
418
+
419
+ ## Current status
420
+
421
+ It is version 0.0.1. It is tested and documented, but not "tested in
422
+ battle", just invented. DSL is subject to be refined and validated,
423
+ everything could change (or broke suddenly). Tests are lot, though.
424
+
425
+ We plan to heavily utilize it for [reality](https://github.com/molybdenum-99/reality),
426
+ that would be serious evaluation of approaches and weeknesses.
427
+
428
+ ## Links
429
+
430
+ * [API Docs](http://www.rubydoc.info/gems/tlaw)
431
+
432
+ ## Author
433
+
434
+ [Victor Shepelev](http://zverok.github.io/)
435
+
436
+ ## License
437
+
438
+ [MIT](./LICENSE.txt).