transprt 0.2.1 → 0.2.2
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.
- checksums.yaml +4 -4
- data/.hound.yml +2 -0
- data/.rubocop.yml +4 -0
- data/.travis.yml +7 -0
- data/Gemfile +0 -1
- data/Gemfile.lock +5 -2
- data/README.md +4 -3
- data/Rakefile +1 -1
- data/lib/transprt.rb +1 -78
- data/lib/transprt/client.rb +87 -0
- data/lib/transprt/rate_limiting.rb +48 -0
- data/test/client_test.rb +33 -9
- data/test/test_helper.rb +0 -1
- data/transprt.gemspec +13 -15
- metadata +40 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 50047652bf414068ce0972cef66b2b1606f98773
|
4
|
+
data.tar.gz: 2af08e598c7326f1aa257f1ee0b03761ec321d65
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d64dd8bf69522d816c2da877982d628552254b3cabfda9fa9109be859ec768f9c9a511aca1d33892f20a9b697a745b83b62128570d3820e51f44d18afebe76ab
|
7
|
+
data.tar.gz: f977ef47116e658412936c0a0da04aa7593a3a9e67d68ab6417e75981fb008af9718e38bc2c2332ce4633f85af76cc80214db7ebfcf21e355b83f7bac469538e
|
data/.hound.yml
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -2,8 +2,8 @@ PATH
|
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
4
|
transprt (0.2.1)
|
5
|
-
json
|
6
|
-
rest-client
|
5
|
+
json
|
6
|
+
rest-client
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: http://rubygems.org/
|
@@ -22,6 +22,7 @@ GEM
|
|
22
22
|
mime-types-data (3.2016.0521)
|
23
23
|
minitest (5.8.4)
|
24
24
|
netrc (0.11.0)
|
25
|
+
rake (11.2.2)
|
25
26
|
rest-client (2.0.0)
|
26
27
|
http-cookie (>= 1.0.2, < 2.0)
|
27
28
|
mime-types (>= 1.16, < 4.0)
|
@@ -39,7 +40,9 @@ PLATFORMS
|
|
39
40
|
ruby
|
40
41
|
|
41
42
|
DEPENDENCIES
|
43
|
+
bundler
|
42
44
|
minitest (~> 5.8.4)
|
45
|
+
rake
|
43
46
|
transprt!
|
44
47
|
webmock (~> 2.1.0)
|
45
48
|
|
data/README.md
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
#transprt
|
2
2
|
|
3
|
-
[](https://badge.fury.io/rb/transprt)
|
4
|
+
[](https://gemnasium.com/github.com/ghn/transprt)
|
5
|
+
[](https://travis-ci.org/ghn/transprt)
|
5
6
|
|
6
7
|
Ruby client for the Swiss public transport API at http://transport.opendata.ch
|
7
8
|
|
@@ -61,6 +62,6 @@ Running the tests
|
|
61
62
|
rake test
|
62
63
|
```
|
63
64
|
|
64
|
-
##
|
65
|
+
## License
|
65
66
|
|
66
67
|
MIT License (MIT)
|
data/Rakefile
CHANGED
data/lib/transprt.rb
CHANGED
@@ -1,78 +1 @@
|
|
1
|
-
require '
|
2
|
-
require 'rest_client'
|
3
|
-
require 'json'
|
4
|
-
|
5
|
-
module Transprt
|
6
|
-
class Client
|
7
|
-
DEFAULT_DOMAIN = 'http://transport.opendata.ch'
|
8
|
-
VERSION = 'v1'
|
9
|
-
|
10
|
-
def initialize(domain=DEFAULT_DOMAIN, version=VERSION)
|
11
|
-
@domain = domain
|
12
|
-
@version = version
|
13
|
-
end
|
14
|
-
|
15
|
-
#
|
16
|
-
# => find locations
|
17
|
-
#
|
18
|
-
def locations(parameters)
|
19
|
-
allowed_parameters = ['query', 'x', 'y', 'type']
|
20
|
-
|
21
|
-
query = create_query(parameters, allowed_parameters)
|
22
|
-
locations = perform('locations', query)
|
23
|
-
|
24
|
-
locations['stations']
|
25
|
-
end
|
26
|
-
|
27
|
-
#
|
28
|
-
# => find connections
|
29
|
-
#
|
30
|
-
def connections(parameters)
|
31
|
-
allowed_parameters = ['from', 'to', 'via', 'date', 'time', 'isArrivalTime', 'transportations', 'limit', 'page',
|
32
|
-
'direct', 'sleeper', 'couchette', 'bike']
|
33
|
-
|
34
|
-
query = create_query(parameters, allowed_parameters)
|
35
|
-
locations = perform('connections', query)
|
36
|
-
|
37
|
-
locations['connections']
|
38
|
-
end
|
39
|
-
|
40
|
-
#
|
41
|
-
# => find station boards
|
42
|
-
#
|
43
|
-
def stationboard(parameters)
|
44
|
-
allowed_parameters = ['station', 'id', 'limit', 'transportations', 'datetime']
|
45
|
-
|
46
|
-
query = create_query(parameters, allowed_parameters)
|
47
|
-
locations = perform('stationboard', query)
|
48
|
-
|
49
|
-
locations['stationboard']
|
50
|
-
end
|
51
|
-
|
52
|
-
private
|
53
|
-
attr_reader :domain, :version
|
54
|
-
|
55
|
-
def perform(endpoint, query)
|
56
|
-
url = "#{create_url(endpoint)}#{query}"
|
57
|
-
response = RestClient.get(url)
|
58
|
-
|
59
|
-
# Uncomment the line below to dump the response in order to generate
|
60
|
-
# a file to use as response stub in tests.
|
61
|
-
# File.write('/tmp/response.json', response)
|
62
|
-
|
63
|
-
JSON.parse(response)
|
64
|
-
end
|
65
|
-
|
66
|
-
def create_url(endpoint)
|
67
|
-
[domain, version, endpoint].join('/') + '?'
|
68
|
-
end
|
69
|
-
|
70
|
-
def create_query(parameters, allowed_parameters)
|
71
|
-
parameters.map do |k,v|
|
72
|
-
next unless allowed_parameters.include?(k.to_s)
|
73
|
-
|
74
|
-
"#{k}=#{URI.escape(v)}"
|
75
|
-
end.join('&')
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
1
|
+
require 'transprt/client'
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rest_client'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
require_relative 'rate_limiting'
|
6
|
+
|
7
|
+
module Transprt
|
8
|
+
class Client
|
9
|
+
DEFAULT_DOMAIN = 'http://transport.opendata.ch'.freeze
|
10
|
+
VERSION = 'v1'.freeze
|
11
|
+
|
12
|
+
def initialize(domain = DEFAULT_DOMAIN, version = VERSION)
|
13
|
+
@domain = domain
|
14
|
+
@version = version
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# => find locations
|
19
|
+
#
|
20
|
+
def locations(parameters)
|
21
|
+
allowed_parameters = %w(query x y type)
|
22
|
+
|
23
|
+
query = create_query(parameters, allowed_parameters)
|
24
|
+
locations = perform('locations', query)
|
25
|
+
|
26
|
+
locations['stations']
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# => find connections
|
31
|
+
#
|
32
|
+
def connections(parameters)
|
33
|
+
allowed_parameters = %w(from to via date time isArrivalTime
|
34
|
+
transportations limit page direct sleeper
|
35
|
+
couchette bike)
|
36
|
+
|
37
|
+
query = create_query(parameters, allowed_parameters)
|
38
|
+
locations = perform('connections', query)
|
39
|
+
|
40
|
+
locations['connections']
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# => find station boards
|
45
|
+
#
|
46
|
+
def stationboard(parameters)
|
47
|
+
allowed_parameters = %w(station id limit transportations datetime)
|
48
|
+
|
49
|
+
query = create_query(parameters, allowed_parameters)
|
50
|
+
locations = perform('stationboard', query)
|
51
|
+
|
52
|
+
locations['stationboard']
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
attr_reader :domain, :version
|
58
|
+
|
59
|
+
def perform(endpoint, query)
|
60
|
+
url = "#{create_url(endpoint)}#{query}"
|
61
|
+
|
62
|
+
response = limiter.get(url)
|
63
|
+
|
64
|
+
# Uncomment the line below to dump the response in order to generate
|
65
|
+
# a file to use as response stub in tests.
|
66
|
+
# File.write('/tmp/response.json', response)
|
67
|
+
|
68
|
+
JSON.parse(response)
|
69
|
+
end
|
70
|
+
|
71
|
+
def create_url(endpoint)
|
72
|
+
[domain, version, endpoint].join('/') + '?'
|
73
|
+
end
|
74
|
+
|
75
|
+
def create_query(parameters, allowed_parameters)
|
76
|
+
parameters.map do |k, v|
|
77
|
+
next unless allowed_parameters.include?(k.to_s)
|
78
|
+
|
79
|
+
"#{k}=#{CGI.escape(v)}"
|
80
|
+
end.join('&')
|
81
|
+
end
|
82
|
+
|
83
|
+
def limiter
|
84
|
+
@limiter ||= RateLimiting.new
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Transprt
|
2
|
+
class RateLimiting
|
3
|
+
# Performs HTTP queries while respecting the rate limit.
|
4
|
+
|
5
|
+
# @param wait_for_quota [Boolean] whether to wait for quota reset
|
6
|
+
# and query again if the rate limit (300 requests/s) is exceeded.
|
7
|
+
def initialize(wait_for_quota = true)
|
8
|
+
@wait_for_quota = wait_for_quota
|
9
|
+
end
|
10
|
+
|
11
|
+
# @return The HTTP response or nil if we're hitting the rate limit and
|
12
|
+
# wait_for_quota is false (@see #initialize).
|
13
|
+
def get(url)
|
14
|
+
begin
|
15
|
+
response = perform_get(url)
|
16
|
+
rescue RestClient::TooManyRequests => e
|
17
|
+
# API uses HTTP 429 to notify us,
|
18
|
+
# @see https://github.com/OpendataCH/Transport/blob/master/lib/Transport/Application.php
|
19
|
+
|
20
|
+
return nil unless wait_for_quota
|
21
|
+
|
22
|
+
sleep_until_quota_reset(e.response)
|
23
|
+
response = perform_get(url)
|
24
|
+
end
|
25
|
+
|
26
|
+
response
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_reader :wait_for_quota
|
32
|
+
|
33
|
+
def perform_get(url)
|
34
|
+
RestClient.get(url)
|
35
|
+
end
|
36
|
+
|
37
|
+
def sleep_until_quota_reset(response)
|
38
|
+
# NOTE We rely on the local clock being synchronized
|
39
|
+
# with the server clock.
|
40
|
+
|
41
|
+
reset_at = response.headers['X-Rate-Limit-Reset'].to_i
|
42
|
+
delta = reset_at - Time.now.to_i
|
43
|
+
|
44
|
+
return if delta < 0
|
45
|
+
sleep delta
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/test/client_test.rb
CHANGED
@@ -2,7 +2,6 @@ require 'test_helper'
|
|
2
2
|
require 'pp'
|
3
3
|
|
4
4
|
class ClientTest < Minitest::Test
|
5
|
-
|
6
5
|
def setup
|
7
6
|
WebMock.reset!
|
8
7
|
|
@@ -18,8 +17,8 @@ class ClientTest < Minitest::Test
|
|
18
17
|
|
19
18
|
first_location = locations.first
|
20
19
|
|
21
|
-
assert first_location['id'] ==
|
22
|
-
assert first_location['name'] ==
|
20
|
+
assert first_location['id'] == '008501008'
|
21
|
+
assert first_location['name'] == 'Genève'
|
23
22
|
end
|
24
23
|
|
25
24
|
def test_connections
|
@@ -38,15 +37,40 @@ class ClientTest < Minitest::Test
|
|
38
37
|
end
|
39
38
|
|
40
39
|
def test_escaping
|
41
|
-
|
42
|
-
|
40
|
+
stub_request(:get, /.*/).to_return(
|
41
|
+
status: 200,
|
42
|
+
body: { connections: nil }.to_json)
|
43
|
+
|
44
|
+
# The following line fails with an URI::InvalidURIError
|
45
|
+
# should the umlaut in 'Zurich' not get escaped.
|
46
|
+
@client.connections from: 'Lausanne', to: 'Zürich'
|
47
|
+
end
|
43
48
|
|
44
|
-
|
49
|
+
def test_rate_limit
|
50
|
+
request_count = 0
|
51
|
+
|
52
|
+
stub_request(:get, /.*/).to_return do
|
53
|
+
request_count += 1
|
54
|
+
|
55
|
+
if request_count == 1 # First request fails.
|
56
|
+
# Mock "rate limit hit" response code
|
57
|
+
{ status: 429,
|
58
|
+
headers: { 'X-Rate-Limit-Reset' => Time.now.to_i.to_s } }
|
59
|
+
elsif request_count == 2 # Second request succeeds.
|
60
|
+
{ status: 200, body: { connections: nil }.to_json }
|
61
|
+
else
|
62
|
+
raise "Did not expect request_count to be #{request_count}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
@client.connections from: 'Lausanne', to: 'Bern'
|
67
|
+
assert request_count == 2
|
45
68
|
end
|
46
69
|
|
47
|
-
def stub_response(name, url
|
48
|
-
# Uncomment lines below should you feel the urge to test against the live
|
49
|
-
# as the stubbing isn't very thorough as of now. (e.g. URLs requested
|
70
|
+
def stub_response(name, url = /.*/, method = :get)
|
71
|
+
# Uncomment lines below should you feel the urge to test against the live
|
72
|
+
# API as the stubbing isn't very thorough as of now. (e.g. URLs requested
|
73
|
+
# aren't checked)
|
50
74
|
# WebMock.allow_net_connect!
|
51
75
|
# return
|
52
76
|
|
data/test/test_helper.rb
CHANGED
data/transprt.gemspec
CHANGED
@@ -1,24 +1,22 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
3
|
-
|
4
1
|
Gem::Specification.new do |gem|
|
5
|
-
gem.name =
|
6
|
-
gem.version = '0.2.
|
7
|
-
gem.authors = [
|
8
|
-
gem.email = [
|
9
|
-
gem.description =
|
10
|
-
gem.summary =
|
2
|
+
gem.name = 'transprt'
|
3
|
+
gem.version = '0.2.2'
|
4
|
+
gem.authors = ['ghn']
|
5
|
+
gem.email = ['ghugon@gmail.com']
|
6
|
+
gem.description = 'Ruby client for the Swiss public transport API'
|
7
|
+
gem.summary = 'Ruby client for the Swiss public transport API'
|
11
8
|
gem.homepage = 'https://github.com/ghn/transprt'
|
12
|
-
gem.license =
|
9
|
+
gem.license = 'MIT'
|
13
10
|
|
14
|
-
gem.files = `git ls-files`.split(
|
11
|
+
gem.files = `git ls-files`.split($RS)
|
15
12
|
gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
16
13
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
|
-
gem.require_paths = [
|
14
|
+
gem.require_paths = ['lib']
|
18
15
|
|
19
|
-
gem.add_dependency('rest-client'
|
20
|
-
gem.add_dependency('json'
|
16
|
+
gem.add_dependency('rest-client')
|
17
|
+
gem.add_dependency('json')
|
18
|
+
gem.add_development_dependency 'bundler'
|
19
|
+
gem.add_development_dependency('rake')
|
21
20
|
gem.add_development_dependency('minitest', '~> 5.8.4')
|
22
21
|
gem.add_development_dependency('webmock', '~> 2.1.0')
|
23
22
|
end
|
24
|
-
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: transprt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ghn
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-07-
|
11
|
+
date: 2016-07-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rest-client
|
@@ -16,34 +16,56 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
20
|
-
- - "<"
|
21
|
-
- !ruby/object:Gem::Version
|
22
|
-
version: 2.1.0
|
19
|
+
version: '0'
|
23
20
|
type: :runtime
|
24
21
|
prerelease: false
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
26
23
|
requirements:
|
27
24
|
- - ">="
|
28
25
|
- !ruby/object:Gem::Version
|
29
|
-
version:
|
30
|
-
- - "<"
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
version: 2.1.0
|
26
|
+
version: '0'
|
33
27
|
- !ruby/object:Gem::Dependency
|
34
28
|
name: json
|
35
29
|
requirement: !ruby/object:Gem::Requirement
|
36
30
|
requirements:
|
37
31
|
- - ">="
|
38
32
|
- !ruby/object:Gem::Version
|
39
|
-
version:
|
33
|
+
version: '0'
|
40
34
|
type: :runtime
|
41
35
|
prerelease: false
|
42
36
|
version_requirements: !ruby/object:Gem::Requirement
|
43
37
|
requirements:
|
44
38
|
- - ">="
|
45
39
|
- !ruby/object:Gem::Version
|
46
|
-
version:
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
47
69
|
- !ruby/object:Gem::Dependency
|
48
70
|
name: minitest
|
49
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +102,9 @@ extensions: []
|
|
80
102
|
extra_rdoc_files: []
|
81
103
|
files:
|
82
104
|
- ".gitignore"
|
105
|
+
- ".hound.yml"
|
106
|
+
- ".rubocop.yml"
|
107
|
+
- ".travis.yml"
|
83
108
|
- Gemfile
|
84
109
|
- Gemfile.lock
|
85
110
|
- LICENSE
|
@@ -87,6 +112,8 @@ files:
|
|
87
112
|
- Rakefile
|
88
113
|
- example.rb
|
89
114
|
- lib/transprt.rb
|
115
|
+
- lib/transprt/client.rb
|
116
|
+
- lib/transprt/rate_limiting.rb
|
90
117
|
- test/client_test.rb
|
91
118
|
- test/responses/client_test_test_connections.json
|
92
119
|
- test/responses/client_test_test_locations.json
|
@@ -115,7 +142,7 @@ rubyforge_project:
|
|
115
142
|
rubygems_version: 2.4.5.1
|
116
143
|
signing_key:
|
117
144
|
specification_version: 4
|
118
|
-
summary: Swiss public transport API
|
145
|
+
summary: Ruby client for the Swiss public transport API
|
119
146
|
test_files:
|
120
147
|
- test/client_test.rb
|
121
148
|
- test/responses/client_test_test_connections.json
|