citysdk 0.1.2.5 → 1.0
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/.gitignore +33 -0
- data/Gemfile.lock +27 -21
- data/README.md +55 -33
- data/bin/csdk +1220 -0
- data/citysdk.gemspec +6 -5
- data/examples/simple_api.rb +68 -0
- data/lib/citysdk/api.rb +105 -105
- data/lib/citysdk/file_reader.rb +195 -120
- data/lib/citysdk/importer.rb +36 -113
- data/lib/citysdk/util.rb +26 -4
- data/lib/citysdk.rb +1 -1
- data/spec/api_spec.rb +41 -34
- data/spec/import_spec.rb +40 -75
- metadata +40 -24
- data/b.sh +0 -4
data/citysdk.gemspec
CHANGED
@@ -19,11 +19,12 @@ Gem::Specification.new do |gem|
|
|
19
19
|
gem.require_paths = ["lib"]
|
20
20
|
|
21
21
|
|
22
|
-
gem.add_dependency('dbf')
|
23
|
-
gem.add_dependency('georuby', '
|
24
|
-
gem.add_dependency('faraday', '
|
25
|
-
gem.add_dependency('charlock_holmes', '
|
22
|
+
gem.add_dependency('dbf', '~> 2.0')
|
23
|
+
gem.add_dependency('georuby', '~> 2.0')
|
24
|
+
gem.add_dependency('faraday', '~> 0.8')
|
25
|
+
gem.add_dependency('charlock_holmes', '~> 0.6')
|
26
|
+
gem.add_dependency('curses', '~> 1.0')
|
26
27
|
|
27
|
-
gem.add_development_dependency "rspec"
|
28
|
+
gem.add_development_dependency "rspec", '~> 3.0'
|
28
29
|
end
|
29
30
|
|
@@ -0,0 +1,68 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# check the wiki for detailed api usage: https://github.com/waagsociety/citysdk-ld/wiki
|
4
|
+
|
5
|
+
require 'citysdk'
|
6
|
+
include CitySDK
|
7
|
+
|
8
|
+
api = API.new('api.citysdk.waag.org') # point to a hosted instance of the api.
|
9
|
+
|
10
|
+
# simple GET
|
11
|
+
# GET requests do not need authentication
|
12
|
+
|
13
|
+
endpoint = api.get '/'
|
14
|
+
puts "Enpoint is: #{endpoint[:features][0][:properties][:title]}."
|
15
|
+
|
16
|
+
first10layers = api.get('/layers')
|
17
|
+
puts "First layer: #{JSON.pretty_generate(first10layers[:features][0])}"
|
18
|
+
|
19
|
+
# find out how many owners the endpoint knows
|
20
|
+
owners = api.get('/owners?per_page=1&count')
|
21
|
+
puts "Number of data maintaners: #{api.last_result[:headers]['x-result-count']}"
|
22
|
+
|
23
|
+
# authenticate for write actions.
|
24
|
+
unless api.authenticate('<name>','<passw>')
|
25
|
+
puts "Did not authenticate..."
|
26
|
+
exit
|
27
|
+
end
|
28
|
+
|
29
|
+
# make a layer
|
30
|
+
# everybody can wite to the temporary 'test' domain:
|
31
|
+
|
32
|
+
layer = {
|
33
|
+
name: "test.cities",
|
34
|
+
owner: "citysdk",
|
35
|
+
title: "Cities: 🏠🏢🏣🏨🏫🏬🏭🏡",
|
36
|
+
description: "Cities big and small.",
|
37
|
+
data_sources: [ "http://fantasy.com" ],
|
38
|
+
authoritative: false,
|
39
|
+
rdf_type: "dbp:City",
|
40
|
+
category: "administrative",
|
41
|
+
subcategory: "cities",
|
42
|
+
licence: "CC0"
|
43
|
+
}
|
44
|
+
|
45
|
+
api.post('/layers',layer)
|
46
|
+
|
47
|
+
# add data to this layer
|
48
|
+
# attach to the node representing the city of Rotterdam
|
49
|
+
object = {type: "Feature",
|
50
|
+
geometry: {
|
51
|
+
type: 'Point',
|
52
|
+
coordinates: [4.4646,51.9222] },
|
53
|
+
properties: {
|
54
|
+
id: 'Rotterdam',
|
55
|
+
title: 'Rotterdam',
|
56
|
+
data: {
|
57
|
+
a: 1,
|
58
|
+
b: 2
|
59
|
+
}
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
63
|
+
api.post('/layers/test.cities/objects', object)
|
64
|
+
|
65
|
+
|
66
|
+
# don't forget to release! this will also send 'unfilled' batches to the backend.
|
67
|
+
api.release
|
68
|
+
|
data/lib/citysdk/api.rb
CHANGED
@@ -1,61 +1,62 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'faraday'
|
3
3
|
|
4
|
+
|
5
|
+
|
4
6
|
module CitySDK
|
7
|
+
|
8
|
+
def log(m)
|
9
|
+
File.open(File.expand_path('~/csdk.log'), "a") do |f|
|
10
|
+
f.write(m + "\n")
|
11
|
+
end
|
12
|
+
end
|
5
13
|
|
6
14
|
class HostException < ::Exception
|
7
15
|
end
|
8
16
|
|
9
17
|
class API
|
10
|
-
attr_reader
|
18
|
+
attr_reader :last_result
|
19
|
+
attr_reader :error
|
11
20
|
attr_accessor :batch_size
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
:params => {}
|
16
|
-
},
|
17
|
-
:nodes => []
|
18
|
-
}
|
21
|
+
attr_accessor :page_size
|
22
|
+
attr_accessor :format
|
23
|
+
|
19
24
|
@@create_tpl = {
|
20
|
-
:
|
21
|
-
|
22
|
-
:create_type => "create"
|
23
|
-
}
|
24
|
-
},
|
25
|
-
:nodes => []
|
25
|
+
type: "FeatureCollection",
|
26
|
+
features: []
|
26
27
|
}
|
27
28
|
|
28
29
|
def initialize(host, port=nil)
|
29
30
|
@error = '';
|
30
31
|
@layer = '';
|
31
|
-
@batch_size =
|
32
|
+
@batch_size = 1000;
|
33
|
+
@page_size = 25;
|
34
|
+
@format = 'jsonld'
|
32
35
|
@updated = @created = 0;
|
33
36
|
set_host(host,port)
|
34
37
|
end
|
35
38
|
|
36
|
-
def authenticate(
|
37
|
-
@
|
39
|
+
def authenticate(n,p)
|
40
|
+
@name = n;
|
38
41
|
@passw = p;
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
else
|
43
|
-
resp = @connection.get '/get_session', { :e => @email, :p => @passw }
|
44
|
-
end
|
45
|
-
if resp.status == 200
|
42
|
+
|
43
|
+
resp = @connection.get '/session', { :name => @name, :password => @passw }
|
44
|
+
if resp.status.between?(200, 299)
|
46
45
|
resp = CitySDK::parseJson(resp.body)
|
47
|
-
if resp[:
|
48
|
-
@connection.headers['X-Auth'] = resp[:
|
46
|
+
if (resp.class == Hash) and resp[:session_key]
|
47
|
+
@connection.headers['X-Auth'] = resp[:session_key]
|
49
48
|
else
|
50
|
-
raise Exception.new(
|
49
|
+
raise Exception.new("Invalid credentials")
|
51
50
|
end
|
52
51
|
else
|
53
52
|
raise Exception.new(resp.body)
|
54
53
|
end
|
55
|
-
|
56
54
|
if block_given?
|
57
|
-
|
58
|
-
|
55
|
+
begin
|
56
|
+
yield
|
57
|
+
ensure
|
58
|
+
self.release
|
59
|
+
end
|
59
60
|
end
|
60
61
|
true
|
61
62
|
end
|
@@ -64,6 +65,8 @@ module CitySDK
|
|
64
65
|
@host = host
|
65
66
|
@port = port
|
66
67
|
|
68
|
+
@host.gsub!(/^http(s)?:\/\//,'')
|
69
|
+
|
67
70
|
if port.nil?
|
68
71
|
if host =~ /^(.*):(\d+)$/
|
69
72
|
@port = $2
|
@@ -73,7 +76,11 @@ module CitySDK
|
|
73
76
|
end
|
74
77
|
end
|
75
78
|
|
76
|
-
@
|
79
|
+
if !($nohttps or @host =~ /.+\.dev/ or @host == 'localhost' or @host == '127.0.0.1' or @host == '0.0.0.0')
|
80
|
+
@connection = Faraday.new :url => "https://#{@host}", :ssl => {:verify => false }
|
81
|
+
else
|
82
|
+
@connection = Faraday.new :url => "http://#{@host}:#{@port}"
|
83
|
+
end
|
77
84
|
@connection.headers = {
|
78
85
|
:user_agent => 'CitySDK_API GEM ' + CitySDK::VERSION,
|
79
86
|
:content_type => 'application/json'
|
@@ -84,16 +91,18 @@ module CitySDK
|
|
84
91
|
raise CitySDK::Exception.new("Trouble connecting to api @ #{host}")
|
85
92
|
end
|
86
93
|
@create = @@create_tpl
|
87
|
-
@match = @@match_tpl
|
88
94
|
end
|
89
|
-
|
90
|
-
def
|
91
|
-
|
92
|
-
|
95
|
+
|
96
|
+
def addFormat(path)
|
97
|
+
if path !~ /format/
|
98
|
+
path = path + ((path =~ /\?/) ? "&" : "?") + "format=#{@format}"
|
99
|
+
end
|
100
|
+
return path if path =~ /page_size/
|
101
|
+
path + "&page_size=#{@page_size}"
|
93
102
|
end
|
94
103
|
|
95
104
|
def set_createTemplate(ctpl)
|
96
|
-
ctpl[:
|
105
|
+
ctpl[:features] = []
|
97
106
|
@create = @@create_tpl = ctpl
|
98
107
|
end
|
99
108
|
|
@@ -101,123 +110,114 @@ module CitySDK
|
|
101
110
|
@layer = l
|
102
111
|
end
|
103
112
|
|
104
|
-
def
|
105
|
-
|
113
|
+
def next
|
114
|
+
@next ? get(@next) : "{}"
|
106
115
|
end
|
107
|
-
|
108
|
-
def
|
109
|
-
|
110
|
-
|
111
|
-
|
116
|
+
|
117
|
+
def layers
|
118
|
+
get "/layers"
|
119
|
+
end
|
120
|
+
|
121
|
+
def owners
|
122
|
+
get "/owners"
|
112
123
|
end
|
113
124
|
|
114
|
-
def
|
115
|
-
|
116
|
-
return match_create_flush if @match[:nodes].length >= @batch_size
|
125
|
+
def objects(layer=nil)
|
126
|
+
!!layer ? get("/layers/#{layer}/objects") : get("/objects")
|
117
127
|
end
|
118
128
|
|
119
|
-
def
|
120
|
-
@create[:
|
121
|
-
create_flush if @create[:
|
129
|
+
def create_object(n)
|
130
|
+
@create[:features] << n
|
131
|
+
create_flush if @create[:features].length >= @batch_size
|
122
132
|
end
|
123
133
|
|
124
134
|
def authorized?
|
125
|
-
|
135
|
+
!! @connection.headers['X-Auth']
|
126
136
|
end
|
127
137
|
|
128
138
|
def release
|
129
|
-
match_flush
|
130
139
|
create_flush # send any remaining entries in the create buffer
|
131
|
-
match_create_flush
|
132
140
|
if authorized?
|
133
|
-
resp = @connection.
|
134
|
-
if resp.status
|
141
|
+
resp = @connection.delete('/session')
|
142
|
+
if resp.status.between?(200, 299)
|
135
143
|
@connection.headers.delete('X-Auth')
|
136
144
|
else
|
137
|
-
@error = CitySDK::parseJson(resp.body)[:
|
145
|
+
@error = CitySDK::parseJson(resp.body)[:error]
|
138
146
|
raise HostException.new(@error)
|
139
147
|
end
|
140
148
|
end
|
141
|
-
return
|
149
|
+
return {created: @created}
|
142
150
|
end
|
143
151
|
|
144
152
|
def delete(path)
|
145
153
|
if authorized?
|
146
|
-
resp = @connection.delete(path)
|
147
|
-
if resp.status
|
148
|
-
|
154
|
+
resp = @connection.delete(addFormat(path))
|
155
|
+
if resp.status.between?(200, 299)
|
156
|
+
@last_result = { status: resp.status, headers: resp.headers }
|
157
|
+
return (resp.body and resp.body !~ /\s*/) ? CitySDK::parseJson(resp.body) : ''
|
149
158
|
end
|
150
|
-
@error = CitySDK::parseJson(resp.body)[:
|
159
|
+
@error = CitySDK::parseJson(resp.body)[:error]
|
151
160
|
raise HostException.new(@error)
|
152
161
|
end
|
153
|
-
raise CitySDK::Exception.new("
|
162
|
+
raise CitySDK::Exception.new("DELETE needs authorization.")
|
154
163
|
end
|
155
164
|
|
156
165
|
def post(path,data)
|
157
166
|
if authorized?
|
158
|
-
resp = @connection.post(path,data.to_json)
|
159
|
-
|
160
|
-
|
167
|
+
resp = @connection.post(addFormat(path),data.to_json)
|
168
|
+
@last_result = { status: resp.status, headers: resp.headers }
|
169
|
+
return CitySDK::parseJson(resp.body) if resp.status.between?(200, 299)
|
170
|
+
@error = resp.body # CitySDK::parseJson(resp.body)[:error]
|
171
|
+
|
172
|
+
File.open(File.expand_path("~/post_error_data.json"),"w") do |fd|
|
173
|
+
fd.write(JSON.pretty_generate({error: @error, data: data}))
|
174
|
+
end
|
175
|
+
|
161
176
|
raise HostException.new(@error)
|
162
177
|
end
|
163
178
|
raise CitySDK::Exception.new("POST needs authorization.")
|
164
179
|
end
|
165
180
|
|
181
|
+
def patch(path,data)
|
182
|
+
if authorized?
|
183
|
+
resp = @connection.patch(addFormat(path),data.to_json)
|
184
|
+
@last_result = { status: resp.status, headers: resp.headers }
|
185
|
+
return CitySDK::parseJson(resp.body) if resp.status.between?(200, 299)
|
186
|
+
@error = CitySDK::parseJson(resp.body)[:error]
|
187
|
+
raise HostException.new(@error)
|
188
|
+
end
|
189
|
+
raise CitySDK::Exception.new("PATCH needs authorization.")
|
190
|
+
end
|
191
|
+
|
166
192
|
def put(path,data)
|
167
193
|
if authorized?
|
168
|
-
resp = @connection.put(path,data.to_json)
|
169
|
-
|
170
|
-
|
194
|
+
resp = @connection.put(addFormat(path),data.to_json)
|
195
|
+
@last_result = { status: resp.status, headers: resp.headers }
|
196
|
+
return CitySDK::parseJson(resp.body) if resp.status.between?(200, 299)
|
197
|
+
@error = CitySDK::parseJson(resp.body)[:error] || {status: resp.status}
|
171
198
|
raise HostException.new(@error)
|
172
199
|
end
|
173
200
|
raise CitySDK::Exception.new("PUT needs authorization.")
|
174
201
|
end
|
175
202
|
|
176
203
|
def get(path)
|
177
|
-
resp = @connection.get(path)
|
178
|
-
|
179
|
-
@
|
204
|
+
resp = @connection.get(addFormat(path))
|
205
|
+
@next = (resp.headers['Link'] =~ /^<(.+)>;\s*rel="next"/) ? $1 : nil
|
206
|
+
@last_result = { status: resp.status, headers: resp.headers }
|
207
|
+
return CitySDK::parseJson(resp.body) if resp.status.between?(200, 299)
|
208
|
+
@error = CitySDK::parseJson(resp.body)[:error]
|
180
209
|
raise HostException.new(@error)
|
181
210
|
end
|
182
|
-
|
183
|
-
def match_create_flush
|
184
|
-
|
185
|
-
if @match[:nodes].length > 0
|
186
|
-
resp = post('util/match',@match)
|
187
|
-
if resp[:nodes].length > 0
|
188
|
-
@create[:nodes] = resp[:nodes]
|
189
|
-
res = put("/nodes/#{@layer}",@create)
|
190
|
-
tally(res)
|
191
|
-
@create[:nodes] = []
|
192
|
-
end
|
193
|
-
@match[:nodes] = []
|
194
|
-
res
|
195
|
-
end
|
196
|
-
nil
|
197
|
-
end
|
198
|
-
|
199
|
-
def match_flush
|
200
|
-
if @match[:nodes].length > 0
|
201
|
-
resp = post('util/match',@match)
|
202
|
-
@match[:nodes] = []
|
203
|
-
return resp
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
|
211
|
+
|
208
212
|
def create_flush
|
209
|
-
if @create[:
|
210
|
-
tally
|
211
|
-
@create[:
|
213
|
+
if @create[:features].length > 0
|
214
|
+
tally post("/layers/#{@layer}/objects",@create)
|
215
|
+
@create[:features] = []
|
212
216
|
end
|
213
217
|
end
|
214
218
|
|
215
219
|
def tally(res)
|
216
|
-
|
217
|
-
# TODO: also tally debug data!
|
218
|
-
@updated += res[:create][:results][:totals][:updated]
|
219
|
-
@created += res[:create][:results][:totals][:created]
|
220
|
-
end
|
220
|
+
@created += res.length
|
221
221
|
end
|
222
222
|
|
223
223
|
end
|