preservation-client 7.4.0 → 8.0.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/.circleci/config.yml +1 -1
- data/Gemfile.lock +17 -13
- data/README.md +5 -0
- data/lib/preservation/client/objects.rb +13 -8
- data/lib/preservation/client/version.rb +1 -1
- data/lib/preservation/client/versioned_api_service.rb +4 -3
- data/lib/preservation/client.rb +42 -6
- data/preservation-client.gemspec +1 -0
- metadata +16 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c187f939eb6d782795bb422308b7417f024db4c4e9d4d36e0685d7060c4150a0
|
|
4
|
+
data.tar.gz: 9c3f855e31677b447712ba3c35d67e0ab1127b83a469fc17664be82ac0b28f70
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9800102447b72c245667cd69cc90c97878a11a28663cb56012d21678355cc4e4e90dccc6e98a8481cab53ec64507e66a5968ef63d01b083b10959e79424bb54b
|
|
7
|
+
data.tar.gz: ab270e3e62cba84ed0e560b63ab392fd48c07854b35f37abb0e0251d471d78e48df5a7cf3d8a78fb93c06a7450ea1656d99624ac1ff283e799a5f18e410c7dec
|
data/.circleci/config.yml
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
preservation-client (
|
|
4
|
+
preservation-client (8.0.0)
|
|
5
5
|
activesupport (>= 4.2)
|
|
6
6
|
faraday (~> 2.0)
|
|
7
|
+
faraday-retry (~> 2.0)
|
|
7
8
|
moab-versioning (>= 5.0.0, < 7)
|
|
8
9
|
zeitwerk (~> 2.1)
|
|
9
10
|
|
|
@@ -31,7 +32,7 @@ GEM
|
|
|
31
32
|
byebug (13.0.0)
|
|
32
33
|
reline (>= 0.6.0)
|
|
33
34
|
coderay (1.1.3)
|
|
34
|
-
concurrent-ruby (1.3.
|
|
35
|
+
concurrent-ruby (1.3.7)
|
|
35
36
|
connection_pool (3.0.2)
|
|
36
37
|
crack (1.0.1)
|
|
37
38
|
bigdecimal
|
|
@@ -40,17 +41,19 @@ GEM
|
|
|
40
41
|
docile (1.4.1)
|
|
41
42
|
drb (2.2.3)
|
|
42
43
|
druid-tools (3.0.0)
|
|
43
|
-
faraday (2.14.
|
|
44
|
+
faraday (2.14.3)
|
|
44
45
|
faraday-net_http (>= 2.0, < 3.5)
|
|
45
46
|
json
|
|
46
47
|
logger
|
|
47
|
-
faraday-net_http (3.4.
|
|
48
|
+
faraday-net_http (3.4.4)
|
|
48
49
|
net-http (~> 0.5)
|
|
50
|
+
faraday-retry (2.4.0)
|
|
51
|
+
faraday (~> 2.0)
|
|
49
52
|
hashdiff (1.2.1)
|
|
50
|
-
i18n (1.
|
|
53
|
+
i18n (1.15.2)
|
|
51
54
|
concurrent-ruby (~> 1.0)
|
|
52
55
|
io-console (0.8.2)
|
|
53
|
-
json (2.
|
|
56
|
+
json (2.20.0)
|
|
54
57
|
language_server-protocol (3.17.0.5)
|
|
55
58
|
lint_roller (1.1.0)
|
|
56
59
|
logger (1.7.0)
|
|
@@ -65,11 +68,11 @@ GEM
|
|
|
65
68
|
nokogiri-happymapper
|
|
66
69
|
net-http (0.9.1)
|
|
67
70
|
uri (>= 0.11.1)
|
|
68
|
-
nokogiri (1.19.
|
|
71
|
+
nokogiri (1.19.4-arm64-darwin)
|
|
69
72
|
racc (~> 1.4)
|
|
70
|
-
nokogiri (1.19.
|
|
73
|
+
nokogiri (1.19.4-x86_64-darwin)
|
|
71
74
|
racc (~> 1.4)
|
|
72
|
-
nokogiri (1.19.
|
|
75
|
+
nokogiri (1.19.4-x86_64-linux-gnu)
|
|
73
76
|
racc (~> 1.4)
|
|
74
77
|
nokogiri-happymapper (0.10.1)
|
|
75
78
|
nokogiri (~> 1.5)
|
|
@@ -106,7 +109,7 @@ GEM
|
|
|
106
109
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
107
110
|
rspec-support (~> 3.13.0)
|
|
108
111
|
rspec-support (3.13.7)
|
|
109
|
-
rubocop (1.
|
|
112
|
+
rubocop (1.88.0)
|
|
110
113
|
json (~> 2.3)
|
|
111
114
|
language_server-protocol (~> 3.17.0.2)
|
|
112
115
|
lint_roller (~> 1.1.0)
|
|
@@ -123,9 +126,10 @@ GEM
|
|
|
123
126
|
rubocop-rake (0.7.1)
|
|
124
127
|
lint_roller (~> 1.1)
|
|
125
128
|
rubocop (>= 1.72.1)
|
|
126
|
-
rubocop-rspec (3.
|
|
129
|
+
rubocop-rspec (3.10.2)
|
|
127
130
|
lint_roller (~> 1.1)
|
|
128
|
-
|
|
131
|
+
regexp_parser (>= 2.0)
|
|
132
|
+
rubocop (~> 1.86, >= 1.86.2)
|
|
129
133
|
ruby-progressbar (1.13.0)
|
|
130
134
|
securerandom (0.4.1)
|
|
131
135
|
simplecov (0.22.0)
|
|
@@ -144,7 +148,7 @@ GEM
|
|
|
144
148
|
addressable (>= 2.8.0)
|
|
145
149
|
crack (>= 0.3.2)
|
|
146
150
|
hashdiff (>= 0.4.0, < 2.0.0)
|
|
147
|
-
zeitwerk (2.
|
|
151
|
+
zeitwerk (2.8.2)
|
|
148
152
|
|
|
149
153
|
PLATFORMS
|
|
150
154
|
arm64-darwin-23
|
data/README.md
CHANGED
|
@@ -60,6 +60,11 @@ See https://github.com/sul-dlss/preservation_catalog#api for info on obtaining a
|
|
|
60
60
|
|
|
61
61
|
Note that the preservation service is behind a firewall.
|
|
62
62
|
|
|
63
|
+
### Retries
|
|
64
|
+
HTTP GET requests will be automatically retried for certain errors. The number of retries (`retries_max`) and interval between retries (`retry_interval`) can be specified as part of the configuration of the client.
|
|
65
|
+
|
|
66
|
+
Note that there is special retry behavior (cleaning up files on failure) for `content_to_file`, but it uses the same configuration.
|
|
67
|
+
|
|
63
68
|
## API Coverage
|
|
64
69
|
|
|
65
70
|
- druids may be with or without the "druid:" prefix - 'oo000oo0000' or 'druid:oo000oo0000'
|
|
@@ -11,6 +11,12 @@ module Preservation
|
|
|
11
11
|
class Client
|
|
12
12
|
# API calls that are about Preserved Objects
|
|
13
13
|
class Objects < VersionedApiService # rubocop:disable Metrics/ClassLength
|
|
14
|
+
def initialize(connection:, streaming_connection:, retry_max:, retry_interval:, api_version: DEFAULT_API_VERSION)
|
|
15
|
+
super(connection: connection, streaming_connection: streaming_connection, api_version: api_version)
|
|
16
|
+
@retry_max = retry_max
|
|
17
|
+
@retry_interval = retry_interval
|
|
18
|
+
end
|
|
19
|
+
|
|
14
20
|
# @param [String] druid - with or without prefix: 'druid:bb123cd4567' OR 'bb123cd4567'
|
|
15
21
|
# @return [Hash] the checksums and filesize for the druid
|
|
16
22
|
def checksum(druid:)
|
|
@@ -69,14 +75,14 @@ module Preservation
|
|
|
69
75
|
# @param [String] destination_filepath - absolute or relative path to desired destination file
|
|
70
76
|
# @param [String] version - the version of the file requested (defaults to nil for latest version)
|
|
71
77
|
# @param [String, nil] expected_md5 - optional expected md5 checksum for integrity validation
|
|
72
|
-
# @param [Integer]
|
|
73
|
-
# @param [Float]
|
|
78
|
+
# @param [Integer] max - number of retry attempts after the initial attempt
|
|
79
|
+
# @param [Float] interval - base delay in seconds for exponential retry backoff
|
|
74
80
|
# @raise [Preservation::Client::IntegrityError] if the expected_md5 is provided and does not match the actual md5
|
|
75
81
|
# @raise [Preservation::Client::NotFoundError] if the specified file is not found
|
|
76
82
|
# @raise [Preservation::Client::Error] for other errors encountered during download
|
|
77
83
|
def content_to_file(druid:, filepath:, destination_filepath:, version: nil, expected_md5: nil, # rubocop:disable Metrics/ParameterLists
|
|
78
|
-
|
|
79
|
-
with_retries(
|
|
84
|
+
max: nil, interval: nil)
|
|
85
|
+
with_retries(max: max || @retry_max, interval: interval || @retry_interval) do
|
|
80
86
|
temp_filepath = nil
|
|
81
87
|
|
|
82
88
|
begin
|
|
@@ -138,16 +144,15 @@ module Preservation
|
|
|
138
144
|
get("objects/#{druid}/file", { category: category, filepath: filepath, version: version }, on_data: on_data)
|
|
139
145
|
end
|
|
140
146
|
|
|
141
|
-
def with_retries(
|
|
147
|
+
def with_retries(max:, interval:)
|
|
142
148
|
attempt = 0
|
|
143
149
|
|
|
144
150
|
begin
|
|
145
151
|
yield
|
|
146
152
|
rescue StandardError => e
|
|
147
|
-
raise if !retryable_error?(e) || attempt >=
|
|
153
|
+
raise if !retryable_error?(e) || attempt >= max
|
|
148
154
|
|
|
149
|
-
|
|
150
|
-
sleep(sleep_seconds) unless sleep_seconds.nil?
|
|
155
|
+
sleep(interval.to_f * (Client::RETRY_BACKOFF_FACTOR**attempt))
|
|
151
156
|
attempt += 1
|
|
152
157
|
retry
|
|
153
158
|
end
|
|
@@ -4,14 +4,15 @@ module Preservation
|
|
|
4
4
|
class Client
|
|
5
5
|
# @abstract API calls to a versioned endpoint
|
|
6
6
|
class VersionedApiService
|
|
7
|
-
def initialize(connection:, api_version: DEFAULT_API_VERSION)
|
|
7
|
+
def initialize(connection:, api_version: DEFAULT_API_VERSION, streaming_connection: nil)
|
|
8
8
|
@connection = connection
|
|
9
9
|
@api_version = api_version
|
|
10
|
+
@streaming_connection = streaming_connection
|
|
10
11
|
end
|
|
11
12
|
|
|
12
13
|
private
|
|
13
14
|
|
|
14
|
-
attr_reader :connection, :api_version
|
|
15
|
+
attr_reader :connection, :api_version, :streaming_connection
|
|
15
16
|
|
|
16
17
|
# @param path [String] path to be appended to connection url (no leading slash)
|
|
17
18
|
def get_json(path, object_id)
|
|
@@ -42,7 +43,7 @@ module Preservation
|
|
|
42
43
|
return http_response(:get, path, params) unless on_data
|
|
43
44
|
|
|
44
45
|
req_url = "#{api_version}/#{path}"
|
|
45
|
-
connection.get("#{api_version}/#{path}", params) do |req|
|
|
46
|
+
(streaming_connection || connection).get("#{api_version}/#{path}", params) do |req|
|
|
46
47
|
req.options.on_data = proc do |chunk, size, env|
|
|
47
48
|
if env.status >= 300
|
|
48
49
|
errmsg = "Preservation::Client.#{caller_locations.first.label} " \
|
data/lib/preservation/client.rb
CHANGED
|
@@ -4,6 +4,7 @@ require 'active_support/core_ext/hash/indifferent_access'
|
|
|
4
4
|
require 'active_support/core_ext/module/delegation'
|
|
5
5
|
require 'active_support/core_ext/object/blank'
|
|
6
6
|
require 'faraday'
|
|
7
|
+
require 'faraday/retry'
|
|
7
8
|
require 'singleton'
|
|
8
9
|
require 'zeitwerk'
|
|
9
10
|
|
|
@@ -53,13 +54,18 @@ module Preservation
|
|
|
53
54
|
|
|
54
55
|
DEFAULT_API_VERSION = 'v1'
|
|
55
56
|
DEFAULT_TIMEOUT = 300
|
|
57
|
+
DEFAULT_RETRY_MAX = 3
|
|
58
|
+
DEFAULT_RETRY_INTERVAL = 0.5
|
|
59
|
+
RETRY_BACKOFF_FACTOR = 2
|
|
56
60
|
TOKEN_HEADER = 'Authorization'
|
|
57
61
|
|
|
58
62
|
include Singleton
|
|
59
63
|
|
|
60
64
|
# @return [Preservation::Client::Objects] an instance of the `Client::Objects` class
|
|
61
65
|
def objects
|
|
62
|
-
@objects ||= Objects.new(connection: connection,
|
|
66
|
+
@objects ||= Objects.new(connection: connection, streaming_connection: streaming_connection,
|
|
67
|
+
retry_max: retry_max, retry_interval: retry_interval,
|
|
68
|
+
api_version: DEFAULT_API_VERSION)
|
|
63
69
|
end
|
|
64
70
|
|
|
65
71
|
# @return [Preservation::Client::Catalog] an instance of the `Client::Catalog` class
|
|
@@ -71,13 +77,19 @@ module Preservation
|
|
|
71
77
|
# @param [String] url the endpoint URL
|
|
72
78
|
# @param [String] token a bearer token for HTTP authentication
|
|
73
79
|
# @param [Integer] read_timeout the value in seconds of the read timeout
|
|
74
|
-
|
|
80
|
+
# @param [Integer] retry_max number of retry attempts for GET requests
|
|
81
|
+
# @param [Float] retry_interval base delay in seconds between retries (exponential backoff)
|
|
82
|
+
def configure(url:, token:, read_timeout: DEFAULT_TIMEOUT,
|
|
83
|
+
retry_max: DEFAULT_RETRY_MAX, retry_interval: DEFAULT_RETRY_INTERVAL)
|
|
75
84
|
instance.url = url
|
|
76
85
|
instance.token = token
|
|
77
86
|
instance.read_timeout = read_timeout
|
|
87
|
+
instance.retry_max = retry_max
|
|
88
|
+
instance.retry_interval = retry_interval
|
|
78
89
|
|
|
79
|
-
# Force
|
|
90
|
+
# Force connections to be re-established when `.configure` is called
|
|
80
91
|
instance.connection = nil
|
|
92
|
+
instance.streaming_connection = nil
|
|
81
93
|
|
|
82
94
|
self
|
|
83
95
|
end
|
|
@@ -85,7 +97,7 @@ module Preservation
|
|
|
85
97
|
delegate :objects, :update, to: :instance
|
|
86
98
|
end
|
|
87
99
|
|
|
88
|
-
attr_writer :connection, :read_timeout, :token, :url
|
|
100
|
+
attr_writer :connection, :read_timeout, :retry_interval, :retry_max, :streaming_connection, :token, :url
|
|
89
101
|
|
|
90
102
|
delegate :update, to: :catalog
|
|
91
103
|
|
|
@@ -103,11 +115,35 @@ module Preservation
|
|
|
103
115
|
@read_timeout || raise(Error, 'read timeout has not been configured')
|
|
104
116
|
end
|
|
105
117
|
|
|
118
|
+
def retry_max
|
|
119
|
+
@retry_max || raise(Error, 'retry_max has not been configured')
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def retry_interval
|
|
123
|
+
@retry_interval || raise(Error, 'retry_interval has not been configured')
|
|
124
|
+
end
|
|
125
|
+
|
|
106
126
|
def connection
|
|
107
|
-
@connection ||=
|
|
127
|
+
@connection ||= build_connection(with_retry: true)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def streaming_connection
|
|
131
|
+
@streaming_connection ||= build_connection(with_retry: false)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def build_connection(with_retry: true) # rubocop:disable Metrics/AbcSize
|
|
135
|
+
Faraday.new(url, request: { read_timeout: read_timeout }) do |builder|
|
|
108
136
|
builder.use ErrorFaradayMiddleware
|
|
137
|
+
if with_retry
|
|
138
|
+
builder.request :retry, max: retry_max,
|
|
139
|
+
interval: retry_interval,
|
|
140
|
+
backoff_factor: RETRY_BACKOFF_FACTOR,
|
|
141
|
+
methods: [:get],
|
|
142
|
+
exceptions: Faraday::Retry::Middleware::DEFAULT_EXCEPTIONS +
|
|
143
|
+
[Faraday::ConnectionFailed, Faraday::SSLError, Faraday::ServerError]
|
|
144
|
+
end
|
|
109
145
|
builder.use Faraday::Request::UrlEncoded
|
|
110
|
-
builder.use Faraday::Response::RaiseError
|
|
146
|
+
builder.use Faraday::Response::RaiseError
|
|
111
147
|
builder.adapter Faraday.default_adapter
|
|
112
148
|
builder.headers[:user_agent] = user_agent
|
|
113
149
|
builder.headers[TOKEN_HEADER] = "Bearer #{token}"
|
data/preservation-client.gemspec
CHANGED
|
@@ -31,6 +31,7 @@ Gem::Specification.new do |spec|
|
|
|
31
31
|
|
|
32
32
|
spec.add_dependency 'activesupport', '>= 4.2'
|
|
33
33
|
spec.add_dependency 'faraday', '~> 2.0'
|
|
34
|
+
spec.add_dependency 'faraday-retry', '~> 2.0'
|
|
34
35
|
spec.add_dependency 'moab-versioning', '>= 5.0.0', '< 7'
|
|
35
36
|
spec.add_dependency 'zeitwerk', '~> 2.1'
|
|
36
37
|
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: preservation-client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 8.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Naomi Dushay
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-06-29 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: activesupport
|
|
@@ -37,6 +37,20 @@ dependencies:
|
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '2.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: faraday-retry
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '2.0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '2.0'
|
|
40
54
|
- !ruby/object:Gem::Dependency
|
|
41
55
|
name: moab-versioning
|
|
42
56
|
requirement: !ruby/object:Gem::Requirement
|