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.
@@ -1,16 +1,17 @@
1
- module ArchivesSpace
1
+ # frozen_string_literal: true
2
2
 
3
+ module ArchivesSpace
3
4
  class Configuration
4
-
5
5
  def defaults
6
6
  {
7
- base_uri: "http://localhost:8089",
8
- base_repo: "",
9
- username: "admin",
10
- password: "admin",
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
- module ArchivesSpace
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
- "Content-Type" => "application/json",
13
- "Content-Length" => "nnnn",
13
+ 'Content-Type' => 'application/json',
14
+ 'Content-Length' => 'nnnn'
14
15
  },
15
16
  put: {
16
- "Content-Type" => "application/json",
17
- "Content-Length" => "nnnn",
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 = "GET", path = "", options = {})
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.has_key? :query
31
+ @options[:query] = {} unless options.key? :query
31
32
 
32
- base_uri = (
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
- module ArchivesSpace
1
+ # frozen_string_literal: true
2
2
 
3
+ module ArchivesSpace
3
4
  class Response
4
- attr_reader :result, :parsed, :body, :headers, :status, :status_code, :xml
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
- module ArchivesSpace
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.process_template(template, data)
10
- t = ERB.new(self.read_template(template))
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.read_template(file)
16
- File.read("#{self.templates_path}/#{file.to_s}.json.erb")
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
- File.join(File.dirname(File.expand_path(__FILE__)), 'templates')
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
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ArchivesSpace
2
4
  class Client
3
- VERSION = "0.1.6"
5
+ VERSION = '0.1.11'
4
6
  end
5
7
  end
@@ -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 "archivesspace/client/helpers"
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
- require "archivesspace/client/client"
9
- require "archivesspace/client/configuration"
10
- require "archivesspace/client/request"
11
- require "archivesspace/client/response"
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 ConnectionError < Exception ; end
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 "Configuration" do
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 "Version information" do
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 "can retrieve the backend version info" do
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 "version"
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
- base_uri: CUSTOM_BASE_URI,
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
- $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
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 = "http://localhost:8089"
8
- CUSTOM_BASE_URI = "https://archives.university.edu/api"
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 = "spec/fixtures/cassettes"
13
+ c.cassette_library_dir = 'spec/fixtures/cassettes'
12
14
  c.hook_into :webmock
13
- c.default_cassette_options = { :record => :once }
14
- end
15
+ c.default_cassette_options = { record: :once }
16
+ end