amplify_syndication 0.2.0 → 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/README.md +18 -0
- data/lib/amplify_syndication/api.rb +191 -92
- data/lib/amplify_syndication/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6b508d426a6c47cead51ad5c59d84322c749a60dc4155add4454a53a388e38e2
|
|
4
|
+
data.tar.gz: 83b82be51f0a21440dfb5ab571a28f11bbbd53e7d23de8ca6b9570a0e6aa9813
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b8ed56083a3c0eab1bcab901a10d8ea6946d0be4c36171387c19a68f23b389580378b6f821629d0197bedd63ddf8c9636b320c97ca85416c59a22795ca00f7fc
|
|
7
|
+
data.tar.gz: 625a208f80f0e38a23a03f75aa2fe7d86bd7a569ab79a0882e5500c4c8376ac1cf6b3ded8547ca48c677efe03a4fce7cef895212c3f80f31e5f86b8511ba9e95
|
data/README.md
CHANGED
|
@@ -153,6 +153,24 @@ puts recent_media
|
|
|
153
153
|
|
|
154
154
|
---
|
|
155
155
|
|
|
156
|
+
## Lookups
|
|
157
|
+
|
|
158
|
+
### Fetch all lookups (simple)
|
|
159
|
+
|
|
160
|
+
Fetch the entire `Lookup` table into memory:
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
api = AmplifySyndication::API.new
|
|
164
|
+
|
|
165
|
+
lookups = api.fetch_all_lookups(
|
|
166
|
+
batch_size: 100,
|
|
167
|
+
sleep_seconds: 2 # optional throttle between API calls
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
puts "Loaded #{lookups.size} lookup rows"
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
156
174
|
## Error Handling
|
|
157
175
|
|
|
158
176
|
If an API call fails, the gem raises a StandardError with details of the HTTP response:
|
|
@@ -1,15 +1,22 @@
|
|
|
1
|
+
require "uri"
|
|
2
|
+
|
|
1
3
|
module AmplifySyndication
|
|
2
4
|
class API
|
|
3
5
|
def initialize(client = Client.new)
|
|
4
6
|
@client = client
|
|
5
7
|
end
|
|
6
8
|
|
|
9
|
+
# === Metadata ===
|
|
10
|
+
|
|
7
11
|
# Fetch metadata
|
|
8
12
|
def fetch_metadata
|
|
9
13
|
@client.get("$metadata?$format=json")
|
|
10
14
|
end
|
|
11
15
|
|
|
12
|
-
#
|
|
16
|
+
# === Field helpers ===
|
|
17
|
+
|
|
18
|
+
# Fetch all Field records for the Property resource in a single call
|
|
19
|
+
# (still paginated under the hood).
|
|
13
20
|
def fetch_property_fields(batch_size: 50, sleep_seconds: 10)
|
|
14
21
|
offset = 0
|
|
15
22
|
fields = []
|
|
@@ -17,57 +24,83 @@ module AmplifySyndication
|
|
|
17
24
|
loop do
|
|
18
25
|
query_options = {
|
|
19
26
|
"$filter" => "ResourceName eq 'Property'",
|
|
20
|
-
"$top"
|
|
21
|
-
"$skip"
|
|
27
|
+
"$top" => batch_size,
|
|
28
|
+
"$skip" => offset
|
|
22
29
|
}.compact
|
|
30
|
+
|
|
23
31
|
response = fetch_with_options("Field", query_options)
|
|
24
|
-
batch
|
|
32
|
+
batch = response["value"] || []
|
|
25
33
|
break if batch.empty?
|
|
26
34
|
|
|
27
35
|
fields.concat(batch)
|
|
28
|
-
|
|
29
36
|
offset += batch_size
|
|
30
37
|
|
|
31
|
-
# Safety sleep between API calls
|
|
32
38
|
sleep(sleep_seconds) if sleep_seconds.positive?
|
|
33
39
|
end
|
|
34
40
|
|
|
35
41
|
fields
|
|
36
42
|
end
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
offset = 0
|
|
40
|
-
values = []
|
|
44
|
+
# === Lookup helpers ===
|
|
41
45
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
query_options["$filter"] = combined_filter
|
|
53
|
-
end
|
|
46
|
+
# Fetch a single Lookup page (batch) with optional filter.
|
|
47
|
+
#
|
|
48
|
+
# filter can be:
|
|
49
|
+
# - a String: "LookupStatus eq 'Active'"
|
|
50
|
+
# - an Array of strings: ["LookupStatus eq 'Active'", "LookupName eq 'PropertyType'"]
|
|
51
|
+
def fetch_lookup_batch(skip:, top: 50, filter: nil)
|
|
52
|
+
query_options = {
|
|
53
|
+
"$top" => top,
|
|
54
|
+
"$skip" => skip
|
|
55
|
+
}
|
|
54
56
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
if filter
|
|
58
|
+
combined_filter =
|
|
59
|
+
filter.is_a?(Array) ? filter.join(" and ") : filter
|
|
60
|
+
query_options["$filter"] = combined_filter
|
|
61
|
+
end
|
|
58
62
|
|
|
59
|
-
|
|
63
|
+
response = fetch_with_options("Lookup", query_options)
|
|
64
|
+
response["value"] || []
|
|
65
|
+
end
|
|
60
66
|
|
|
61
|
-
|
|
67
|
+
# Iterate over Lookup records in batches, yielding each batch.
|
|
68
|
+
#
|
|
69
|
+
# Example:
|
|
70
|
+
# api.each_lookup_batch(batch_size: 100, filter: "LookupStatus eq 'Active'") do |batch|
|
|
71
|
+
# batch.each { |row| VowLookup.upsert_from_row(row) }
|
|
72
|
+
# end
|
|
73
|
+
def each_lookup_batch(batch_size: 50, sleep_seconds: 10, filter: nil)
|
|
74
|
+
skip = 0
|
|
75
|
+
|
|
76
|
+
loop do
|
|
77
|
+
batch = fetch_lookup_batch(skip: skip, top: batch_size, filter: filter)
|
|
78
|
+
break if batch.empty?
|
|
62
79
|
|
|
63
|
-
|
|
80
|
+
yield(batch) if block_given?
|
|
81
|
+
|
|
82
|
+
skip += batch_size
|
|
64
83
|
sleep(sleep_seconds) if sleep_seconds.positive?
|
|
65
84
|
end
|
|
85
|
+
end
|
|
66
86
|
|
|
67
|
-
|
|
87
|
+
# Fetch all Lookup rows into memory (simple usage).
|
|
88
|
+
#
|
|
89
|
+
# For long-running syncs, prefer each_lookup_batch so your app can
|
|
90
|
+
# handle persistence/checkpointing per batch.
|
|
91
|
+
def fetch_all_lookups(batch_size: 50, sleep_seconds: 10, filter: nil)
|
|
92
|
+
results = []
|
|
93
|
+
|
|
94
|
+
each_lookup_batch(batch_size: batch_size,
|
|
95
|
+
sleep_seconds: sleep_seconds,
|
|
96
|
+
filter: filter) do |batch|
|
|
97
|
+
results.concat(batch)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
results
|
|
68
101
|
end
|
|
69
102
|
|
|
70
|
-
#
|
|
103
|
+
# Get all rows for a single LookupName (convenience helper).
|
|
71
104
|
def lookup(lookup_name, batch_size: 50, sleep_seconds: 10)
|
|
72
105
|
offset = 0
|
|
73
106
|
values = []
|
|
@@ -75,29 +108,31 @@ module AmplifySyndication
|
|
|
75
108
|
loop do
|
|
76
109
|
query_options = {
|
|
77
110
|
"$filter" => "LookupName eq '#{lookup_name}'",
|
|
78
|
-
"$top"
|
|
79
|
-
"$skip"
|
|
111
|
+
"$top" => batch_size,
|
|
112
|
+
"$skip" => offset
|
|
80
113
|
}.compact
|
|
114
|
+
|
|
81
115
|
response = fetch_with_options("Lookup", query_options)
|
|
82
|
-
batch
|
|
116
|
+
batch = response["value"] || []
|
|
83
117
|
break if batch.empty?
|
|
84
118
|
|
|
85
119
|
values.concat(batch)
|
|
86
120
|
offset += batch_size
|
|
87
121
|
|
|
88
|
-
# Safety sleep between API calls
|
|
89
122
|
sleep(sleep_seconds) if sleep_seconds.positive?
|
|
90
123
|
end
|
|
91
124
|
|
|
92
125
|
values
|
|
93
126
|
end
|
|
94
127
|
|
|
95
|
-
#
|
|
128
|
+
# === Property helpers ===
|
|
129
|
+
|
|
130
|
+
# Fetch basic property data (simple test helper)
|
|
96
131
|
def fetch_property_data(limit = 1)
|
|
97
132
|
@client.get("Property", "$top" => limit)
|
|
98
133
|
end
|
|
99
134
|
|
|
100
|
-
# Fetch data with query options
|
|
135
|
+
# Fetch data with query options against an arbitrary resource
|
|
101
136
|
def fetch_with_options(resource, query_options = {})
|
|
102
137
|
@client.get_with_options(resource, query_options)
|
|
103
138
|
end
|
|
@@ -112,6 +147,7 @@ module AmplifySyndication
|
|
|
112
147
|
"$skip" => skip,
|
|
113
148
|
"$count" => count
|
|
114
149
|
}.compact
|
|
150
|
+
|
|
115
151
|
fetch_with_options("Property", query_options)
|
|
116
152
|
end
|
|
117
153
|
|
|
@@ -120,82 +156,142 @@ module AmplifySyndication
|
|
|
120
156
|
fetch_filtered_properties(count: "true", top: 0)
|
|
121
157
|
end
|
|
122
158
|
|
|
123
|
-
|
|
159
|
+
# === Replication helpers ===
|
|
160
|
+
#
|
|
161
|
+
# These three methods give you flexible control:
|
|
162
|
+
#
|
|
163
|
+
# - fetch_initial_download_batch -> single page
|
|
164
|
+
# - each_initial_download_batch -> yields per page
|
|
165
|
+
# - perform_initial_download -> fetch everything into memory
|
|
124
166
|
|
|
125
|
-
#
|
|
126
|
-
def
|
|
167
|
+
# Build and fetch a single replication batch for a resource.
|
|
168
|
+
def fetch_initial_download_batch(
|
|
127
169
|
resource: "Property",
|
|
128
170
|
batch_size: 100,
|
|
129
171
|
fields: ["ModificationTimestamp", "ListingKey"],
|
|
130
172
|
filter: nil,
|
|
131
|
-
|
|
132
|
-
checkpoint: { last_timestamp: "1970-01-01T00:00:00Z", last_key: 0 },
|
|
173
|
+
checkpoint: { last_timestamp: "1970-01-01T00:00:00Z", last_key: 0 }
|
|
133
174
|
)
|
|
134
|
-
|
|
135
|
-
all_records = [] # Array to collect all records
|
|
175
|
+
encoded_ts = URI.encode_www_form_component(checkpoint[:last_timestamp])
|
|
136
176
|
|
|
137
|
-
|
|
138
|
-
|
|
177
|
+
# checkpoint filter: everything strictly after the last (timestamp, key) pair
|
|
178
|
+
checkpoint_filter = "(ModificationTimestamp gt #{encoded_ts}) " \
|
|
179
|
+
"or (ModificationTimestamp eq #{encoded_ts} and ListingKey gt '#{checkpoint[:last_key]}')"
|
|
139
180
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
batch_filter << "(ModificationTimestamp gt #{URI.encode_www_form_component(checkpoint[:last_timestamp])})"
|
|
144
|
-
batch_filter << "or (ModificationTimestamp eq #{URI.encode_www_form_component(checkpoint[:last_timestamp])} and ListingKey gt '#{checkpoint[:last_key]}')"
|
|
145
|
-
batch_filter = batch_filter.join(" ")
|
|
181
|
+
conditions = []
|
|
182
|
+
conditions << "(#{filter})" if filter
|
|
183
|
+
conditions << "(#{checkpoint_filter})"
|
|
146
184
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
185
|
+
query_options = {
|
|
186
|
+
"$select" => fields.join(","),
|
|
187
|
+
"$filter" => conditions.join(" and "),
|
|
188
|
+
"$orderby" => "ModificationTimestamp,ListingKey",
|
|
189
|
+
"$top" => batch_size
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
response = fetch_with_options(resource, query_options)
|
|
193
|
+
response["value"] || []
|
|
194
|
+
end
|
|
154
195
|
|
|
155
|
-
|
|
156
|
-
|
|
196
|
+
# Iterate over replication batches, yielding [batch, checkpoint].
|
|
197
|
+
#
|
|
198
|
+
# You can persist checkpoint per batch to resume later if something fails.
|
|
199
|
+
def each_initial_download_batch(
|
|
200
|
+
resource: "Property",
|
|
201
|
+
batch_size: 100,
|
|
202
|
+
fields: ["ModificationTimestamp", "ListingKey"],
|
|
203
|
+
filter: nil,
|
|
204
|
+
sleep_seconds: 10,
|
|
205
|
+
checkpoint: { last_timestamp: "1970-01-01T00:00:00Z", last_key: 0 }
|
|
206
|
+
)
|
|
207
|
+
loop do
|
|
208
|
+
batch = fetch_initial_download_batch(
|
|
209
|
+
resource: resource,
|
|
210
|
+
batch_size: batch_size,
|
|
211
|
+
fields: fields,
|
|
212
|
+
filter: filter,
|
|
213
|
+
checkpoint: checkpoint
|
|
214
|
+
)
|
|
157
215
|
|
|
158
|
-
|
|
159
|
-
response = fetch_with_options(resource, query_options)
|
|
160
|
-
records = response["value"]
|
|
161
|
-
break if records.empty?
|
|
216
|
+
break if batch.empty?
|
|
162
217
|
|
|
163
|
-
|
|
164
|
-
all_records.concat(records)
|
|
218
|
+
yield(batch, checkpoint) if block_given?
|
|
165
219
|
|
|
166
|
-
# Update checkpoint
|
|
167
|
-
last_record =
|
|
220
|
+
# Update checkpoint automatically based on the last record in the batch
|
|
221
|
+
last_record = batch.last
|
|
168
222
|
checkpoint[:last_timestamp] = last_record["ModificationTimestamp"]
|
|
169
|
-
checkpoint[:last_key]
|
|
223
|
+
checkpoint[:last_key] = last_record["ListingKey"]
|
|
170
224
|
|
|
171
|
-
|
|
172
|
-
break if records.size < batch_size
|
|
225
|
+
break if batch.size < batch_size
|
|
173
226
|
|
|
174
|
-
# Safety sleep between API calls
|
|
175
227
|
sleep(sleep_seconds) if sleep_seconds.positive?
|
|
176
228
|
end
|
|
177
|
-
|
|
178
|
-
puts "Initial download complete."
|
|
179
|
-
all_records # Return the collected records
|
|
180
229
|
end
|
|
181
230
|
|
|
182
|
-
#
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
231
|
+
# Perform initial download for replication, buffering all results
|
|
232
|
+
# into memory (simple usage).
|
|
233
|
+
#
|
|
234
|
+
# For large datasets, prefer each_initial_download_batch.
|
|
235
|
+
def perform_initial_download(
|
|
236
|
+
resource: "Property",
|
|
237
|
+
batch_size: 100,
|
|
238
|
+
fields: ["ModificationTimestamp", "ListingKey"],
|
|
239
|
+
filter: nil,
|
|
240
|
+
sleep_seconds: 10,
|
|
241
|
+
checkpoint: { last_timestamp: "1970-01-01T00:00:00Z", last_key: 0 }
|
|
242
|
+
)
|
|
243
|
+
results = []
|
|
244
|
+
|
|
245
|
+
puts "Starting initial download..."
|
|
246
|
+
|
|
247
|
+
each_initial_download_batch(
|
|
191
248
|
resource: resource,
|
|
192
249
|
batch_size: batch_size,
|
|
193
250
|
fields: fields,
|
|
194
251
|
filter: filter,
|
|
252
|
+
sleep_seconds: sleep_seconds,
|
|
195
253
|
checkpoint: checkpoint
|
|
196
|
-
) do |batch|
|
|
197
|
-
|
|
198
|
-
|
|
254
|
+
) do |batch, _checkpoint|
|
|
255
|
+
results.concat(batch)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
puts "Initial download complete."
|
|
259
|
+
results
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Fetch updates since the last checkpoint.
|
|
263
|
+
#
|
|
264
|
+
# If a block is given, yields each batch; otherwise returns all
|
|
265
|
+
# updates in a single array (same as perform_initial_download).
|
|
266
|
+
def fetch_updates(
|
|
267
|
+
resource: "Property",
|
|
268
|
+
batch_size: 100,
|
|
269
|
+
fields: ["ModificationTimestamp", "ListingKey"],
|
|
270
|
+
filter: nil,
|
|
271
|
+
checkpoint: { last_timestamp: "1970-01-01T00:00:00Z", last_key: 0 },
|
|
272
|
+
sleep_seconds: 10
|
|
273
|
+
)
|
|
274
|
+
if block_given?
|
|
275
|
+
each_initial_download_batch(
|
|
276
|
+
resource: resource,
|
|
277
|
+
batch_size: batch_size,
|
|
278
|
+
fields: fields,
|
|
279
|
+
filter: filter,
|
|
280
|
+
sleep_seconds: sleep_seconds,
|
|
281
|
+
checkpoint: checkpoint
|
|
282
|
+
) do |batch, cp|
|
|
283
|
+
yield(batch, cp)
|
|
284
|
+
end
|
|
285
|
+
nil
|
|
286
|
+
else
|
|
287
|
+
perform_initial_download(
|
|
288
|
+
resource: resource,
|
|
289
|
+
batch_size: batch_size,
|
|
290
|
+
fields: fields,
|
|
291
|
+
filter: filter,
|
|
292
|
+
sleep_seconds: sleep_seconds,
|
|
293
|
+
checkpoint: checkpoint
|
|
294
|
+
)
|
|
199
295
|
end
|
|
200
296
|
end
|
|
201
297
|
|
|
@@ -206,7 +302,7 @@ module AmplifySyndication
|
|
|
206
302
|
@client.get(endpoint)
|
|
207
303
|
end
|
|
208
304
|
|
|
209
|
-
|
|
305
|
+
# === Media helpers ===
|
|
210
306
|
|
|
211
307
|
# Fetch a media record by MediaKey
|
|
212
308
|
def fetch_media_by_key(media_key)
|
|
@@ -222,22 +318,25 @@ module AmplifySyndication
|
|
|
222
318
|
batch_size: 100
|
|
223
319
|
)
|
|
224
320
|
query_options = {
|
|
225
|
-
"$filter"
|
|
321
|
+
"$filter" => "#{filter} and ModificationTimestamp ge #{modification_date}",
|
|
226
322
|
"$orderby" => orderby,
|
|
227
|
-
"$top"
|
|
323
|
+
"$top" => batch_size
|
|
228
324
|
}
|
|
325
|
+
|
|
229
326
|
fetch_with_options("Media", query_options)
|
|
230
327
|
end
|
|
231
328
|
|
|
232
329
|
# Fetch media by ResourceName and ResourceRecordKey
|
|
233
330
|
def fetch_media_by_resource(resource_name, resource_key, batch_size = 100)
|
|
234
331
|
filter = "ResourceRecordKey eq '#{resource_key}' and ResourceName eq '#{resource_name}'"
|
|
332
|
+
|
|
235
333
|
query_options = {
|
|
236
|
-
"$filter"
|
|
334
|
+
"$filter" => filter,
|
|
237
335
|
"$orderby" => "ModificationTimestamp,MediaKey",
|
|
238
|
-
"$top"
|
|
336
|
+
"$top" => batch_size
|
|
239
337
|
}
|
|
338
|
+
|
|
240
339
|
fetch_with_options("Media", query_options)
|
|
241
340
|
end
|
|
242
341
|
end
|
|
243
|
-
end
|
|
342
|
+
end
|