ezid-client 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a0032991c2394702b7a0ad25e6c3c515ad3cf149
4
- data.tar.gz: b45b2ef5d65adbcb9a297b42303f313db55af520
3
+ metadata.gz: 141f157b6169fdedba53b58d11e0b9c1b26c7662
4
+ data.tar.gz: ef0923245729d7f20e079f3581d7647bc7cb5581
5
5
  SHA512:
6
- metadata.gz: 08d97b5a15168e921e585d926d4e42579911df6fe46f337791b60809d178906d3ccc8dfc772d20eb79b9894b0b1e5e671e07ce6755dee2f8f3372de4fd48ddf2
7
- data.tar.gz: e0649750e6f411e69a86aa8fc4b5abc32fff0085c4303af41e0157a990a5cf8e42310019c21a840c54a733a3512a4ab0c3c31bcaf94cf5b26b6c535aede98f08
6
+ metadata.gz: 16b22cbb1ba43ab241036710cfb044f66b365dd434a783a31a1370743e063517a530df12b2f72c24c0f3575dabdb8045e49fa673fc01306621d46fe2a8a88683
7
+ data.tar.gz: f329532cc4d162575b0c3c14a76d8b7ca253963486686e73565ec4f31debd1c498f5804034f2713badac901b0875d7a27eba322a488bd6ef48f073b01246ce70
@@ -1,3 +1,4 @@
1
+ sudo: false
1
2
  language: ruby
2
3
  rvm:
3
4
  - 2.2
data/README.md CHANGED
@@ -113,21 +113,24 @@ I, [2014-12-04T15:12:48.853964 #86734] INFO -- : EZID DeleteIdentifier -- succe
113
113
 
114
114
  ## Batch Download
115
115
 
116
- Instantiate an `Ezid::Client` and call `batch_download` with hash options -- see http://ezid.cdlib.org/doc/apidoc.html#parameters. Repeated values should be given as an array value for the parameter key.
117
-
118
- Note that, due to the asynchronous nature of this request, the response only returns the URL at which the batch will be available to download (as described in the EZID documentation). Use the `notify` option to specify one or more email addresses to receive notification when the download file is actually available.
119
-
120
- **Example**
121
-
122
- ```
123
- >> c = Ezid::Client.new
124
- => #<Ezid::Client connection=#<Net::HTTP ezid.cdlib.org:443 open=false> user="eziduser" session=CLOSED>
125
- >> response = c.batch_download(format: "csv", notify: "eziduser@example.com", column: ["_id", "_target", "_status", "_profile", "_export", "_created", "_updated"], convertTimestamps: "yes", permanence: "real", owner: "eziduser")
126
- I, [2015-02-20T15:16:53.462660 #55850] INFO -- : EZID BatchDownload -- success: http://ezid.cdlib.org/download/473deecb96.csv.gz
127
- => #<Net::HTTPOK 200 OK readbody=true>
128
- >> response.download_url
129
- => "http://ezid.cdlib.org/download/da543b91a0.csv.gz"
130
- ```
116
+ See http://ezid.cdlib.org/doc/apidoc.html#parameters. Repeated values should be given as an array value for the parameter key.
117
+
118
+ ```
119
+ >> batch = Ezid::BatchDownload.new(:csv)
120
+ => #<Ezid::BatchDownload format=:csv>
121
+ >> batch.column = ["_id", "_target"]
122
+ => ["_id", "_target"]
123
+ >> batch.createdAfter = Date.today.to_time
124
+ => 2016-02-24 00:00:00 -0500
125
+ >> batch
126
+ => #<Ezid::BatchDownload column=["_id", "_target"] createdAfter=1456290000 format=:csv>
127
+ >> batch.download_url
128
+ I, [2016-02-24T18:03:40.828005 #1084] INFO -- : EZID BatchDownload -- success: http://ezid.cdlib.org/download/4a63401e17.csv.gz
129
+ => "http://ezid.cdlib.org/download/4a63401e17.csv.gz"
130
+ >> batch.download_file
131
+ File successfully download to /current/working/directory/4a63401e17.csv.gz.
132
+ => nil
133
+ ```
131
134
 
132
135
  ## Metadata handling
133
136
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.0
1
+ 1.3.0
@@ -0,0 +1,131 @@
1
+ require "hashie"
2
+ require "net/http"
3
+ require "uri"
4
+ require_relative "reserved_metadata"
5
+
6
+ module Ezid
7
+ class BatchDownloadError < Error; end
8
+
9
+ class BatchDownload < Hashie::Dash
10
+ include Hashie::Extensions::Coercion
11
+
12
+ ANVL = "anvl".freeze
13
+ CSV = "csv".freeze
14
+ XML = "xml".freeze
15
+ FORMATS = [ ANVL, CSV, XML ].freeze
16
+
17
+ YES = "yes".freeze
18
+ NO = "no".freeze
19
+ BOOLEANS = [ YES, NO ].freeze
20
+
21
+ TEST = "test".freeze
22
+ REAL = "real".freeze
23
+ PERMANENCE = [ TEST, REAL ].freeze
24
+
25
+ ARK = "ark".freeze
26
+ DOI = "doi".freeze
27
+ URN = "urn".freeze
28
+ TYPES = [ ARK, DOI, URN, ].freeze
29
+
30
+ # CSV Columns
31
+ ID = "_id".freeze
32
+ MAPPED_CREATOR = "_mappedCreator".freeze
33
+ MAPPED_TITLE = "_mappedTitle".freeze
34
+ MAPPED_PUBLISHER = "_mappedPublisher".freeze
35
+ MAPPED_DATE = "_mappedDate".freeze
36
+ MAPPED_TYPE = "_mappedType".freeze
37
+
38
+ MAX_DOWNLOAD_TRIES = 300
39
+ DOWNLOAD_RETRY_INTERVAL = 1
40
+
41
+ # Parameters
42
+ property :format, required: true # {anvl|csv|xml}
43
+ property :column # repeatable
44
+ property :notify # repeatable
45
+ property :convertTimestamps # {yes|no}
46
+
47
+ # Search constraints
48
+ property :createdAfter
49
+ property :createdBefore
50
+ property :crossref # {yes|no}
51
+ property :exported # {yes|no}
52
+ property :owner # repeatable
53
+ property :ownergroup # repeatable
54
+ property :permanence # {test|real}
55
+ property :profile # (repeatable)
56
+ property :status # {reserved|public|unavailable} (repeatable)
57
+ property :type # {ark|doi|urn} (repeatable)
58
+ property :updatedAfter
59
+ property :updatedBefore
60
+
61
+ coerce_value FalseClass, ->(v) { NO }
62
+ coerce_value TrueClass, ->(v) { YES }
63
+ coerce_value DateTime, ->(v) { v.to_time.utc.iso8601 }
64
+ coerce_value Time, Integer
65
+
66
+ def initialize(format, args={})
67
+ super(args.merge(format: format))
68
+ end
69
+
70
+ def params
71
+ to_h
72
+ end
73
+
74
+ def get_response
75
+ @response ||= client.batch_download(params)
76
+ end
77
+
78
+ def reload
79
+ @response = nil
80
+ end
81
+
82
+ def download_url
83
+ get_response.download_url
84
+ end
85
+
86
+ def download_file(path: nil)
87
+ path ||= Dir.getwd
88
+ fullpath = File.directory?(path) ? File.join(path, download_filename) : path
89
+ tries = 0
90
+ begin
91
+ tries += 1
92
+ download = Net::HTTP.get_response(download_uri)
93
+ download.value
94
+ rescue Net::HTTPServerException => e
95
+ if download.is_a?(Net::HTTPNotFound)
96
+ if tries < MAX_DOWNLOAD_TRIES
97
+ print "Download file not yet available (attempt #{tries} of #{MAX_DOWNLOAD_TRIES})."
98
+ puts " Trying again in #{DOWNLOAD_RETRY_INTERVAL} second(s) ..."
99
+ sleep DOWNLOAD_RETRY_INTERVAL
100
+ retry
101
+ else
102
+ raise BatchDownloadError,
103
+ "Maximum download attempts (#{MAX_DOWNLOAD_TRIES}) reached unsuccessfully."
104
+ end
105
+ else
106
+ raise
107
+ end
108
+ else
109
+ File.open(fullpath, "wb") do |f|
110
+ f.write(download.body)
111
+ end
112
+ puts "File successfully download to #{fullpath}."
113
+ end
114
+ end
115
+
116
+ private
117
+
118
+ def download_uri
119
+ URI(download_url)
120
+ end
121
+
122
+ def download_filename
123
+ File.basename(download_uri.path)
124
+ end
125
+
126
+ def client
127
+ Client.new
128
+ end
129
+
130
+ end
131
+ end
@@ -1,11 +1,13 @@
1
1
  require "net/http"
2
2
 
3
+ require_relative "error"
4
+ require_relative "status"
3
5
  require_relative "configuration"
4
6
  require_relative "session"
5
7
  require_relative "metadata"
6
8
  require_relative "identifier"
7
9
  require_relative "proxy_identifier"
8
- require_relative "error"
10
+ require_relative "batch_download"
9
11
 
10
12
  Dir[File.expand_path("../responses/*.rb", __FILE__)].each { |m| require m }
11
13
  Dir[File.expand_path("../requests/*.rb", __FILE__)].each { |m| require m }
@@ -14,11 +14,6 @@ module Ezid
14
14
  # Attributes to display on inspect
15
15
  INSPECT_ATTRS = %w( id status target created ).freeze
16
16
 
17
- # EZID status terms
18
- PUBLIC = "public".freeze
19
- RESERVED = "reserved".freeze
20
- UNAVAILABLE = "unavailable".freeze
21
-
22
17
  class << self
23
18
  attr_accessor :defaults
24
19
 
@@ -151,19 +146,19 @@ module Ezid
151
146
  # Is the identifier reserved?
152
147
  # @return [Boolean]
153
148
  def reserved?
154
- status == RESERVED
149
+ status == Status::RESERVED
155
150
  end
156
151
 
157
152
  # Is the identifier public?
158
153
  # @return [Boolean]
159
154
  def public?
160
- status == PUBLIC
155
+ status == Status::PUBLIC
161
156
  end
162
157
 
163
158
  # Is the identifier unavailable?
164
159
  # @return [Boolean]
165
160
  def unavailable?
166
- status =~ /^#{UNAVAILABLE}/
161
+ status.to_s.start_with? Status::UNAVAILABLE
167
162
  end
168
163
 
169
164
  # Is the identifier deletable?
@@ -182,7 +177,7 @@ module Ezid
182
177
  if unavailable? and reason.nil?
183
178
  return
184
179
  end
185
- value = UNAVAILABLE
180
+ value = Status::UNAVAILABLE
186
181
  if reason
187
182
  value += " | #{reason}"
188
183
  end
@@ -192,7 +187,7 @@ module Ezid
192
187
  # Mark the identifier as public
193
188
  # @return [String] the new status
194
189
  def public!
195
- self.status = PUBLIC
190
+ self.status = Status::PUBLIC
196
191
  end
197
192
 
198
193
  protected
@@ -1,4 +1,5 @@
1
1
  require "hashie"
2
+ require_relative "reserved_metadata"
2
3
 
3
4
  module Ezid
4
5
  #
@@ -7,6 +8,7 @@ module Ezid
7
8
  # @api private
8
9
  #
9
10
  class Metadata < Hashie::Mash
11
+ include ReservedMetadata
10
12
 
11
13
  # EZID metadata field/value separator
12
14
  ANVL_SEPARATOR = ": "
@@ -27,16 +29,11 @@ module Ezid
27
29
  LINE_CONTINUATION_RE = /\r?\n\s+/
28
30
  # A line ending
29
31
  LINE_ENDING_RE = /\r?\n/
30
- # EZID reserved metadata elements that are read-only
31
- # @see http://ezid.cdlib.org/doc/apidoc.html#internal-metadata
32
- READONLY = %w( _owner _ownergroup _shadows _shadowedby _datacenter _created _updated ).freeze
33
- # EZID metadata profiles
34
- # @see http://ezid.cdlib.org/doc/apidoc.html#metadata-profiles
35
- # @note crossref is not included because it is a simple element
36
- PROFILES = %w( dc datacite erc ).freeze
37
- RESERVED_ALIASES = [ :coowners=, :export=, :profile=, :status=, :target=,
38
- :coowners, :export, :profile, :status, :target,
39
- :datacenter, :owner, :ownergroup, :shadowedby, :shadows ]
32
+ # @api private
33
+ RESERVED_ALIASES = %w(
34
+ coowners datacenter export owner ownergroup
35
+ profile shadowedby shadows status target
36
+ ).freeze
40
37
 
41
38
  def initialize(data={})
42
39
  super coerce(data)
@@ -75,39 +72,20 @@ module Ezid
75
72
 
76
73
  protected
77
74
 
78
- def method_missing(name, *args, &block)
79
- if reserved_alias?(name)
80
- reserved_alias(name, *args)
81
- elsif profile_accessor?(name)
82
- profile_accessor(name, *args)
75
+ # Overrides Hashie::Mash
76
+ def convert_key(key)
77
+ k = super
78
+ if RESERVED_ALIASES.include?(k)
79
+ "_#{k}"
80
+ elsif k =~ /\A(dc|datacite|erc)_/
81
+ k.sub(/_/, ".")
83
82
  else
84
- super
83
+ k
85
84
  end
86
85
  end
87
86
 
88
87
  private
89
88
 
90
- def reserved_alias?(name)
91
- RESERVED_ALIASES.include?(name)
92
- end
93
-
94
- def reserved_alias(name, *args)
95
- send("_#{name}", *args)
96
- end
97
-
98
- def profile_accessor?(name)
99
- PROFILES.include? name.to_s.split("_").first
100
- end
101
-
102
- def profile_accessor(name, *args)
103
- key = name.to_s.sub("_", ".")
104
- if key.end_with?("=")
105
- self[key[0..-2]] = args.first
106
- else
107
- self[key]
108
- end
109
- end
110
-
111
89
  def to_time(value)
112
90
  time = value.to_i
113
91
  (time == 0) ? nil : Time.at(time).utc
@@ -0,0 +1,26 @@
1
+ module Ezid
2
+ #
3
+ # EZID reserved metadata elements
4
+ #
5
+ # @see http://ezid.cdlib.org/doc/apidoc.html#internal-metadata
6
+ #
7
+ module ReservedMetadata
8
+ COOWNERS = "_coowners".freeze
9
+ CREATED = "_created".freeze
10
+ DATACENTER = "_datacenter".freeze
11
+ EXPORT = "_export".freeze
12
+ OWNER = "_owner".freeze
13
+ OWNERGROUP = "_ownergroup".freeze
14
+ PROFILE = "_profile".freeze
15
+ SHADOWEDBY = "_shadowedby".freeze
16
+ SHADOWS = "_shadows".freeze
17
+ STATUS = "_status".freeze
18
+ TARGET = "_target".freeze
19
+ UPDATED = "_updated".freeze
20
+
21
+ # Read-only elements
22
+ READONLY = [
23
+ CREATED, DATACENTER, OWNER, OWNERGROUP, SHADOWEDBY, SHADOWS, UPDATED
24
+ ].freeze
25
+ end
26
+ end
@@ -0,0 +1,10 @@
1
+ module Ezid
2
+ #
3
+ # EZID status terms
4
+ #
5
+ module Status
6
+ PUBLIC = "public".freeze
7
+ RESERVED = "reserved".freeze
8
+ UNAVAILABLE = "unavailable".freeze
9
+ end
10
+ end
@@ -119,7 +119,7 @@ module Ezid
119
119
 
120
120
  describe "#delete" do
121
121
  context "when the identifier is reserved" do
122
- subject { described_class.new(id: "id", status: Identifier::RESERVED) }
122
+ subject { described_class.new(id: "id", status: Status::RESERVED) }
123
123
  context "and is persisted" do
124
124
  before { allow(subject).to receive(:persisted?) { true } }
125
125
  it "deletes the identifier" do
@@ -136,7 +136,7 @@ module Ezid
136
136
  end
137
137
  end
138
138
  context "when identifier is not reserved" do
139
- subject { described_class.new(id: "id", status: Identifier::PUBLIC) }
139
+ subject { described_class.new(id: "id", status: Status::PUBLIC) }
140
140
  it "raises an exception" do
141
141
  expect { subject.delete }.to raise_error(Error)
142
142
  end
@@ -193,7 +193,7 @@ module Ezid
193
193
  it { is_expected.not_to be_unavailable }
194
194
  end
195
195
  context "when the identifier is reserved" do
196
- before { subject.status = Identifier::RESERVED }
196
+ before { subject.status = Status::RESERVED }
197
197
  it { is_expected.not_to be_public }
198
198
  it { is_expected.to be_reserved }
199
199
  it { is_expected.not_to be_unavailable }
@@ -218,7 +218,7 @@ module Ezid
218
218
  subject { described_class.new(id: "id", status: status) }
219
219
  describe "#unavailable!" do
220
220
  context "when the status is \"unavailable\"" do
221
- let(:status) { "#{Identifier::UNAVAILABLE} | whatever" }
221
+ let(:status) { "#{Status::UNAVAILABLE} | whatever" }
222
222
  context "and no reason is given" do
223
223
  it "logs a warning" do
224
224
  pending "https://github.com/duke-libraries/ezid-client/issues/46"
@@ -238,12 +238,12 @@ module Ezid
238
238
  subject.unavailable!("because")
239
239
  end
240
240
  it "should change the status" do
241
- expect { subject.unavailable!("because") }.to change(subject, :status).from(status).to("#{Identifier::UNAVAILABLE} | because")
241
+ expect { subject.unavailable!("because") }.to change(subject, :status).from(status).to("#{Status::UNAVAILABLE} | because")
242
242
  end
243
243
  end
244
244
  end
245
245
  context "when the status is \"reserved\"" do
246
- let(:status) { Identifier::RESERVED }
246
+ let(:status) { Status::RESERVED }
247
247
  context "and persisted" do
248
248
  before { allow(subject).to receive(:persisted?) { true } }
249
249
  it "raises an exception" do
@@ -253,28 +253,28 @@ module Ezid
253
253
  context "and not persisted" do
254
254
  before { allow(subject).to receive(:persisted?) { false } }
255
255
  it "changes the status" do
256
- expect { subject.unavailable! }.to change(subject, :status).from(Identifier::RESERVED).to(Identifier::UNAVAILABLE)
256
+ expect { subject.unavailable! }.to change(subject, :status).from(Status::RESERVED).to(Status::UNAVAILABLE)
257
257
  end
258
258
  end
259
259
  end
260
260
  context "when the status is \"public\"" do
261
- let(:status) { Identifier::PUBLIC }
261
+ let(:status) { Status::PUBLIC }
262
262
  context "and no reason is given" do
263
263
  it "changes the status" do
264
- expect { subject.unavailable! }.to change(subject, :status).from(Identifier::PUBLIC).to(Identifier::UNAVAILABLE)
264
+ expect { subject.unavailable! }.to change(subject, :status).from(Status::PUBLIC).to(Status::UNAVAILABLE)
265
265
  end
266
266
  end
267
267
  context "and a reason is given" do
268
268
  it "changes the status and appends the reason" do
269
- expect { subject.unavailable!("withdrawn") }.to change(subject, :status).from(Identifier::PUBLIC).to("#{Identifier::UNAVAILABLE} | withdrawn")
269
+ expect { subject.unavailable!("withdrawn") }.to change(subject, :status).from(Status::PUBLIC).to("#{Status::UNAVAILABLE} | withdrawn")
270
270
  end
271
271
  end
272
272
  end
273
273
  end
274
274
  describe "#public!" do
275
- subject { described_class.new(id: "id", status: Identifier::UNAVAILABLE) }
275
+ subject { described_class.new(id: "id", status: Status::UNAVAILABLE) }
276
276
  it "changes the status" do
277
- expect { subject.public! }.to change(subject, :status).from(Identifier::UNAVAILABLE).to(Identifier::PUBLIC)
277
+ expect { subject.public! }.to change(subject, :status).from(Status::UNAVAILABLE).to(Status::PUBLIC)
278
278
  end
279
279
  end
280
280
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ezid-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Chandek-Stark
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-11-19 00:00:00.000000000 Z
11
+ date: 2016-02-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hashie
@@ -83,6 +83,7 @@ files:
83
83
  - VERSION
84
84
  - ezid-client.gemspec
85
85
  - lib/ezid-client.rb
86
+ - lib/ezid/batch_download.rb
86
87
  - lib/ezid/client.rb
87
88
  - lib/ezid/configuration.rb
88
89
  - lib/ezid/error.rb
@@ -101,6 +102,7 @@ files:
101
102
  - lib/ezid/requests/modify_identifier_request.rb
102
103
  - lib/ezid/requests/request.rb
103
104
  - lib/ezid/requests/server_status_request.rb
105
+ - lib/ezid/reserved_metadata.rb
104
106
  - lib/ezid/responses/batch_download_response.rb
105
107
  - lib/ezid/responses/create_identifier_response.rb
106
108
  - lib/ezid/responses/delete_identifier_response.rb
@@ -114,6 +116,7 @@ files:
114
116
  - lib/ezid/responses/response.rb
115
117
  - lib/ezid/responses/server_status_response.rb
116
118
  - lib/ezid/session.rb
119
+ - lib/ezid/status.rb
117
120
  - lib/ezid/test_helper.rb
118
121
  - spec/integration/client_spec.rb
119
122
  - spec/integration/identifier_spec.rb
@@ -143,7 +146,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
143
146
  version: '0'
144
147
  requirements: []
145
148
  rubyforge_project:
146
- rubygems_version: 2.4.6
149
+ rubygems_version: 2.4.3
147
150
  signing_key:
148
151
  specification_version: 4
149
152
  summary: Ruby client for EZID API Version 2