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,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).