archivesspace-client 0.1.6 → 0.1.11
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/Gemfile +2 -0
- data/README.md +59 -5
- data/Rakefile +11 -3
- data/archivesspace-client.gemspec +25 -19
- data/examples/export.rb +20 -17
- data/examples/password_reset.rb +8 -7
- data/examples/repo_and_user.rb +32 -29
- data/examples/test_connection.rb +15 -11
- data/examples/user_groups.rb +44 -0
- data/exe/asclient +7 -0
- data/features/exec.feature +4 -0
- data/features/support/setup.rb +9 -0
- data/features/version.feature +12 -0
- data/lib/archivesspace/client/cli/exec.rb +44 -0
- data/lib/archivesspace/client/cli/version.rb +16 -0
- data/lib/archivesspace/client/cli.rb +24 -0
- data/lib/archivesspace/client/client.rb +33 -10
- data/lib/archivesspace/client/configuration.rb +10 -10
- data/lib/archivesspace/client/pagination.rb +60 -0
- data/lib/archivesspace/client/request.rb +11 -13
- data/lib/archivesspace/client/response.rb +4 -6
- data/lib/archivesspace/client/task.rb +70 -0
- data/lib/archivesspace/client/template.rb +14 -13
- data/lib/archivesspace/client/version.rb +3 -1
- data/lib/archivesspace/client.rb +24 -12
- data/spec/archivesspace/client_spec.rb +57 -10
- data/spec/archivesspace/configuration_spec.rb +6 -6
- data/spec/archivesspace/templates_spec.rb +26 -0
- data/spec/spec_helper.rb +8 -6
- metadata +106 -22
- data/lib/archivesspace/client/helpers.rb +0 -126
@@ -1,16 +1,17 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module ArchivesSpace
|
3
4
|
class Configuration
|
4
|
-
|
5
5
|
def defaults
|
6
6
|
{
|
7
|
-
base_uri:
|
8
|
-
base_repo:
|
9
|
-
|
10
|
-
|
7
|
+
base_uri: 'http://localhost:8089',
|
8
|
+
base_repo: '',
|
9
|
+
debug: false,
|
10
|
+
username: 'admin',
|
11
|
+
password: 'admin',
|
11
12
|
page_size: 50,
|
12
13
|
throttle: 0,
|
13
|
-
verify_ssl: true
|
14
|
+
verify_ssl: true
|
14
15
|
}
|
15
16
|
end
|
16
17
|
|
@@ -18,11 +19,10 @@ module ArchivesSpace
|
|
18
19
|
settings = defaults.merge(settings)
|
19
20
|
settings.each do |property, value|
|
20
21
|
next unless defaults.keys.include? property
|
22
|
+
|
21
23
|
instance_variable_set("@#{property}", value)
|
22
24
|
self.class.send(:attr_accessor, property)
|
23
25
|
end
|
24
26
|
end
|
25
|
-
|
26
27
|
end
|
27
|
-
|
28
|
-
end
|
28
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ArchivesSpace
|
4
|
+
# Handle API Pagination using enumerator
|
5
|
+
module Pagination
|
6
|
+
# TODO: get via lookup of endpoints that support pagination? (nice-to-have)
|
7
|
+
ENDPOINTS = %w[
|
8
|
+
accessions
|
9
|
+
agents/corporate_entities
|
10
|
+
agents/families
|
11
|
+
agents/people
|
12
|
+
agents/software
|
13
|
+
archival_objects
|
14
|
+
digital_objects
|
15
|
+
groups
|
16
|
+
repositiories
|
17
|
+
resources
|
18
|
+
subjects
|
19
|
+
users
|
20
|
+
]
|
21
|
+
|
22
|
+
ENDPOINTS.each do |endpoint|
|
23
|
+
method_name = endpoint.split('/').last # remove prefix
|
24
|
+
define_method(method_name) do |options = {}|
|
25
|
+
all(endpoint, options)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def all(path, options = {})
|
30
|
+
Enumerator.new do |yielder|
|
31
|
+
page = 1
|
32
|
+
unlimited_listing = false
|
33
|
+
loop do
|
34
|
+
options[:query] ||= {}
|
35
|
+
options[:query][:page] = page
|
36
|
+
result = get(path, options)
|
37
|
+
results = []
|
38
|
+
|
39
|
+
if result.parsed.respond_to?(:key) && result.parsed.key?('results')
|
40
|
+
results = result.parsed['results']
|
41
|
+
else
|
42
|
+
results = result.parsed
|
43
|
+
unlimited_listing = true
|
44
|
+
end
|
45
|
+
|
46
|
+
if results.any?
|
47
|
+
results.each do |i|
|
48
|
+
yielder << i
|
49
|
+
end
|
50
|
+
raise StopIteration if unlimited_listing
|
51
|
+
|
52
|
+
page += 1
|
53
|
+
else
|
54
|
+
raise StopIteration
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end.lazy
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module ArchivesSpace
|
3
4
|
class Request
|
4
5
|
include HTTParty
|
5
6
|
attr_reader :config, :headers, :method, :path, :options
|
@@ -9,37 +10,34 @@ module ArchivesSpace
|
|
9
10
|
delete: {},
|
10
11
|
get: {},
|
11
12
|
post: {
|
12
|
-
|
13
|
-
|
13
|
+
'Content-Type' => 'application/json',
|
14
|
+
'Content-Length' => 'nnnn'
|
14
15
|
},
|
15
16
|
put: {
|
16
|
-
|
17
|
-
|
17
|
+
'Content-Type' => 'application/json',
|
18
|
+
'Content-Length' => 'nnnn'
|
18
19
|
}
|
19
20
|
}
|
20
21
|
headers[method]
|
21
22
|
end
|
22
23
|
|
23
|
-
def initialize(config, method =
|
24
|
+
def initialize(config, method = 'GET', path = '', options = {})
|
24
25
|
@config = config
|
25
26
|
@method = method.downcase.to_sym
|
26
|
-
@path = path.gsub(
|
27
|
+
@path = path.gsub(%r{^/+}, '')
|
27
28
|
@options = options
|
28
29
|
@options[:headers] = options[:headers] ? default_headers(@method).merge(options[:headers]) : default_headers(@method)
|
29
30
|
@options[:verify] = config.verify_ssl
|
30
|
-
@options[:query] = {} unless options.
|
31
|
+
@options[:query] = {} unless options.key? :query
|
31
32
|
|
32
|
-
|
33
|
-
config.base_repo.nil? or config.base_repo.empty?
|
34
|
-
) ? config.base_uri : "#{config.base_uri}/#{config.base_repo}"
|
33
|
+
self.class.debug_output($stdout) if @config.debug
|
35
34
|
|
35
|
+
base_uri = config.base_repo&.length&.positive? ? File.join(config.base_uri, config.base_repo) : config.base_uri
|
36
36
|
self.class.base_uri base_uri
|
37
37
|
end
|
38
38
|
|
39
39
|
def execute
|
40
40
|
self.class.send method, "/#{path}", options
|
41
41
|
end
|
42
|
-
|
43
42
|
end
|
44
|
-
|
45
43
|
end
|
@@ -1,10 +1,10 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module ArchivesSpace
|
3
4
|
class Response
|
4
|
-
attr_reader :result, :parsed, :body, :headers, :status, :status_code
|
5
|
+
attr_reader :result, :parsed, :body, :headers, :status, :status_code
|
5
6
|
|
6
7
|
def initialize(result)
|
7
|
-
# throw error
|
8
8
|
@result = result
|
9
9
|
@parsed = result.parsed_response
|
10
10
|
@body = result.body
|
@@ -12,7 +12,5 @@ module ArchivesSpace
|
|
12
12
|
@status = result.response
|
13
13
|
@status_code = result.code.to_i
|
14
14
|
end
|
15
|
-
|
16
15
|
end
|
17
|
-
|
18
|
-
end
|
16
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ArchivesSpace
|
4
|
+
# Perform specific API tasks
|
5
|
+
module Task
|
6
|
+
# def batch_import(payload, params = {})
|
7
|
+
# # TODO: create "batch_import", payload, params
|
8
|
+
# end
|
9
|
+
|
10
|
+
def group_user_assignment(users_with_roles)
|
11
|
+
updated = []
|
12
|
+
groups.each do |group|
|
13
|
+
group = get("groups/#{uri_to_id(group['uri'])}").parsed
|
14
|
+
update = false
|
15
|
+
|
16
|
+
users_with_roles.each do |user, roles|
|
17
|
+
# should the user still belong to this group?
|
18
|
+
if group['member_usernames'].include?(user)
|
19
|
+
unless roles.include? group['group_code']
|
20
|
+
group['member_usernames'].delete user
|
21
|
+
update = true
|
22
|
+
end
|
23
|
+
# should the user be added to this group?
|
24
|
+
elsif roles.include? group['group_code']
|
25
|
+
group['member_usernames'] << user
|
26
|
+
update = true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
next unless update
|
31
|
+
|
32
|
+
response = post("/groups/#{uri_to_id(group['uri'])}", group.to_json)
|
33
|
+
updated << response
|
34
|
+
end
|
35
|
+
updated
|
36
|
+
end
|
37
|
+
|
38
|
+
def login
|
39
|
+
username = config.username
|
40
|
+
password = config.password
|
41
|
+
base_repo = config.base_repo
|
42
|
+
use_global_repository # ensure we're in the global scope to login
|
43
|
+
result = request('POST', "/users/#{username}/login", { query: { password: password } })
|
44
|
+
unless result.parsed['session']
|
45
|
+
raise ConnectionError, "API client login failed as user [#{username}], check username and password are correct"
|
46
|
+
end
|
47
|
+
|
48
|
+
config.base_repo = base_repo # reset repo as set by the cfg
|
49
|
+
@token = result.parsed['session']
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def password_reset(username, password)
|
54
|
+
user = all('users').find { |u| u['username'] == username }
|
55
|
+
raise RequestError, user.status unless user
|
56
|
+
|
57
|
+
post(user['uri'], user.to_json, { password: password })
|
58
|
+
end
|
59
|
+
|
60
|
+
# def search(params)
|
61
|
+
# # TODO: get "search", params
|
62
|
+
# end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def uri_to_id(uri)
|
67
|
+
uri.split('/').last
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -1,25 +1,26 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module ArchivesSpace
|
3
4
|
module Template
|
4
|
-
|
5
5
|
def self.list
|
6
|
-
|
6
|
+
Dir.glob File.join(templates_path, '*.erb')
|
7
7
|
end
|
8
8
|
|
9
|
-
def self.
|
10
|
-
t = ERB.new(
|
11
|
-
r = t.result(binding).gsub(/\n+/,"\n")
|
12
|
-
JSON.parse(r)
|
9
|
+
def self.process(template, data)
|
10
|
+
t = ERB.new(read(template))
|
11
|
+
r = t.result(binding).gsub(/\n+/, "\n")
|
12
|
+
JSON.parse(r).to_json
|
13
13
|
end
|
14
14
|
|
15
|
-
def self.
|
16
|
-
File.read("#{
|
15
|
+
def self.read(file)
|
16
|
+
File.read("#{templates_path}/#{file}.json.erb")
|
17
17
|
end
|
18
18
|
|
19
19
|
def self.templates_path
|
20
|
-
|
20
|
+
ENV.fetch(
|
21
|
+
'ARCHIVESSPACE_CLIENT_TEMPLATES_PATH',
|
22
|
+
File.join(File.dirname(File.expand_path(__FILE__)), 'templates')
|
23
|
+
)
|
21
24
|
end
|
22
|
-
|
23
25
|
end
|
24
|
-
|
25
|
-
end
|
26
|
+
end
|
data/lib/archivesspace/client.rb
CHANGED
@@ -1,22 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/cli'
|
1
4
|
require 'httparty'
|
2
5
|
require 'json'
|
3
6
|
require 'nokogiri'
|
4
7
|
|
5
8
|
# mixins required first
|
6
|
-
require
|
9
|
+
require 'archivesspace/client/pagination'
|
10
|
+
require 'archivesspace/client/task'
|
11
|
+
|
12
|
+
require 'archivesspace/client/client'
|
13
|
+
require 'archivesspace/client/configuration'
|
14
|
+
require 'archivesspace/client/request'
|
15
|
+
require 'archivesspace/client/response'
|
16
|
+
require 'archivesspace/client/template'
|
17
|
+
require 'archivesspace/client/version'
|
7
18
|
|
8
|
-
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require "archivesspace/client/template"
|
13
|
-
require "archivesspace/client/version"
|
19
|
+
# cli
|
20
|
+
require 'archivesspace/client/cli/exec'
|
21
|
+
require 'archivesspace/client/cli/version'
|
22
|
+
require 'archivesspace/client/cli' # load the registry last
|
14
23
|
|
15
24
|
module ArchivesSpace
|
25
|
+
class ConnectionError < RuntimeError; end
|
26
|
+
|
27
|
+
class ContextError < RuntimeError; end
|
28
|
+
|
29
|
+
class RepositoryIdError < RuntimeError; end
|
16
30
|
|
17
|
-
class
|
18
|
-
class ContextError < Exception ; end
|
19
|
-
class ParamsError < Exception ; end
|
20
|
-
class RequestError < Exception ; end
|
31
|
+
class ParamsError < RuntimeError; end
|
21
32
|
|
22
|
-
end
|
33
|
+
class RequestError < RuntimeError; end
|
34
|
+
end
|
@@ -1,43 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe ArchivesSpace::Client do
|
4
|
-
|
5
6
|
let(:client) { ArchivesSpace::Client.new }
|
6
7
|
let(:login) { -> { client.login } }
|
7
8
|
|
8
|
-
describe
|
9
|
-
|
9
|
+
describe 'Configuration' do
|
10
10
|
it 'will use the default configuration if none is provided' do
|
11
11
|
client = ArchivesSpace::Client.new
|
12
12
|
expect(client.config.base_uri).to eq DEFAULT_BASE_URI
|
13
13
|
end
|
14
14
|
|
15
15
|
it 'will raise an error if supplied configuration is of invalid type' do
|
16
|
-
expect{ ArchivesSpace::Client.new({ base_uri: CUSTOM_BASE_URI }) }.to raise_error(RuntimeError)
|
16
|
+
expect { ArchivesSpace::Client.new({ base_uri: CUSTOM_BASE_URI }) }.to raise_error(RuntimeError)
|
17
17
|
end
|
18
18
|
|
19
19
|
it 'will allow a configuration object to be provided' do
|
20
20
|
client = ArchivesSpace::Client.new(ArchivesSpace::Configuration.new({ base_uri: CUSTOM_BASE_URI }))
|
21
21
|
expect(client.config.base_uri).to eq CUSTOM_BASE_URI
|
22
22
|
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'Repository scoping' do
|
26
|
+
it 'will set the repository with an integer id' do
|
27
|
+
client = ArchivesSpace::Client.new
|
28
|
+
client.repository 2
|
29
|
+
expect(client.config.base_repo).to eq 'repositories/2'
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'will set the repository with a string id cast to integer' do
|
33
|
+
client = ArchivesSpace::Client.new
|
34
|
+
client.repository '2'
|
35
|
+
expect(client.config.base_repo).to eq 'repositories/2'
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'will fail if the id cannot be cast to integer' do
|
39
|
+
client = ArchivesSpace::Client.new
|
40
|
+
expect { client.repository('xyz') }.to raise_error(
|
41
|
+
ArchivesSpace::RepositoryIdError
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'will use the global repo if repository is passed nil' do
|
46
|
+
client = ArchivesSpace::Client.new
|
47
|
+
client.repository 2
|
48
|
+
client.repository nil
|
49
|
+
expect(client.config.base_repo).to eq ''
|
50
|
+
end
|
23
51
|
|
52
|
+
it 'will use the global repo when the method is called' do
|
53
|
+
client = ArchivesSpace::Client.new
|
54
|
+
client.repository 2
|
55
|
+
client.use_global_repository
|
56
|
+
expect(client.config.base_repo).to eq ''
|
57
|
+
end
|
24
58
|
end
|
25
59
|
|
26
|
-
describe
|
60
|
+
describe 'Pagination' do
|
61
|
+
it 'will have a method for defined paginated record types' do
|
62
|
+
client = ArchivesSpace::Client.new
|
63
|
+
ArchivesSpace::Pagination::ENDPOINTS.each do |e|
|
64
|
+
next if e.match?('/')
|
65
|
+
|
66
|
+
expect(client.respond_to?(e.to_sym)).to be true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'will have a method for defined paginated record types with multipart path' do
|
71
|
+
client = ArchivesSpace::Client.new
|
72
|
+
expect(client.respond_to?(:people)).to be true
|
73
|
+
end
|
74
|
+
end
|
27
75
|
|
76
|
+
describe 'Version information' do
|
28
77
|
it 'has a version number' do
|
29
78
|
expect(ArchivesSpace::Client::VERSION).not_to be nil
|
30
79
|
end
|
31
80
|
|
32
|
-
it
|
81
|
+
it 'can retrieve the backend version info' do
|
33
82
|
VCR.use_cassette('backend_version') do
|
34
83
|
login.call
|
35
|
-
response = client.get
|
84
|
+
response = client.get 'version'
|
36
85
|
expect(response.status_code).to eq(200)
|
37
86
|
expect(response.body).to match(/ArchivesSpace \(.*\)/)
|
38
87
|
end
|
39
88
|
end
|
40
|
-
|
41
89
|
end
|
42
|
-
|
43
|
-
end
|
90
|
+
end
|
@@ -1,7 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe ArchivesSpace::Configuration do
|
4
|
-
|
5
6
|
it 'uses the default profile for configuration settings' do
|
6
7
|
config = ArchivesSpace::Configuration.new
|
7
8
|
expect(config.base_uri).to eq DEFAULT_BASE_URI
|
@@ -9,8 +10,8 @@ describe ArchivesSpace::Configuration do
|
|
9
10
|
|
10
11
|
it 'allows configuration settings to be provided' do
|
11
12
|
config = ArchivesSpace::Configuration.new({
|
12
|
-
|
13
|
-
|
13
|
+
base_uri: CUSTOM_BASE_URI
|
14
|
+
})
|
14
15
|
expect(config.base_uri).to eq CUSTOM_BASE_URI
|
15
16
|
end
|
16
17
|
|
@@ -22,7 +23,6 @@ describe ArchivesSpace::Configuration do
|
|
22
23
|
|
23
24
|
it 'ignores unrecognized configuration properties' do
|
24
25
|
config = ArchivesSpace::Configuration.new({ xyz: 123 })
|
25
|
-
expect{ config.xyz }.to raise_error(NoMethodError)
|
26
|
+
expect { config.xyz }.to raise_error(NoMethodError)
|
26
27
|
end
|
27
|
-
|
28
|
-
end
|
28
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe ArchivesSpace::Template do
|
6
|
+
it 'can list the default templates' do
|
7
|
+
templates = ArchivesSpace::Template.list
|
8
|
+
expect(templates).to_not be_empty
|
9
|
+
expect(templates).to include(/repository_with_agent.*erb/)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'can change the path when template envvar is set' do
|
13
|
+
expect(ArchivesSpace::Template.templates_path).to match(
|
14
|
+
/#{File.join('lib', 'archivesspace', 'client', 'templates')}/
|
15
|
+
)
|
16
|
+
ENV['ARCHIVESSPACE_CLIENT_TEMPLATES_PATH'] = '/path/to/nowhere'
|
17
|
+
expect(ArchivesSpace::Template.templates_path).to eq '/path/to/nowhere'
|
18
|
+
ENV.delete('ARCHIVESSPACE_CLIENT_TEMPLATES_PATH')
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'can process a template' do
|
22
|
+
data = { repo_code: 'ABC', name: 'ABC Archive', agent_contact_name: 'ABC Admin' }
|
23
|
+
json = JSON.parse(ArchivesSpace::Template.process(:repository_with_agent, data))
|
24
|
+
expect(json['repository']['repo_code']).to eq data[:repo_code]
|
25
|
+
end
|
26
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,14 +1,16 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
2
4
|
require 'archivesspace/client'
|
3
5
|
require 'vcr'
|
4
6
|
require 'webmock/rspec'
|
5
7
|
|
6
8
|
# GLOBAL VALUES FOR SPECS
|
7
|
-
DEFAULT_BASE_URI =
|
8
|
-
CUSTOM_BASE_URI =
|
9
|
+
DEFAULT_BASE_URI = 'http://localhost:8089'
|
10
|
+
CUSTOM_BASE_URI = 'https://archives.university.edu/api'
|
9
11
|
|
10
12
|
VCR.configure do |c|
|
11
|
-
c.cassette_library_dir =
|
13
|
+
c.cassette_library_dir = 'spec/fixtures/cassettes'
|
12
14
|
c.hook_into :webmock
|
13
|
-
c.default_cassette_options = { :
|
14
|
-
end
|
15
|
+
c.default_cassette_options = { record: :once }
|
16
|
+
end
|