dynamoid_lockable 1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 20dc4301053a630ebf779584c76f14020d6c544d923cffa359adc0cc6cc10785
4
+ data.tar.gz: 7f808f3a937e3ee7fd92b1dc4baceb796c15ae2a805b293dc8c451521f2eaf8a
5
+ SHA512:
6
+ metadata.gz: 538ce9bf0e7753d337b8625613c9c201c16a4311831fe0f7172335c61bae2fc13c41b90a460e5f0d00d28522edebd3337db89d0f835c37e97433dd3f1fede9dc
7
+ data.tar.gz: 3c844120ea632d237da9d45becfb90162718fd111cb1917a359cb8f07d2f0bc6bd81db347a5397e2ac050ffa24751e620bcf46303480def41f431a526e024636
@@ -0,0 +1,100 @@
1
+ version: 2.0
2
+
3
+ workflows:
4
+ version: 2
5
+ build:
6
+ jobs:
7
+ - "ruby-2.5"
8
+ - "ruby-2.6"
9
+ - "ruby-2.7"
10
+
11
+ jobs:
12
+ "ruby-2.5":
13
+ docker:
14
+ - image: ruby:2.5
15
+ - image: amazon/dynamodb-local
16
+ environment:
17
+ MAX_HEAP_SIZE: 1024m
18
+ HEAP_NEWSIZE: 512m
19
+
20
+ steps:
21
+ - checkout
22
+ - restore_cache:
23
+ keys:
24
+ - v1-dependencies-{{ checksum "Gemfile.lock" }}-{{ checksum "Appraisals" }}
25
+ # fallback to using the latest cache if no exact match is found
26
+ - v1-dependencies-
27
+
28
+ - run:
29
+ name: install dependencies
30
+ command: |
31
+ gem update bundler
32
+ bundle install
33
+ bundle exec appraisal install
34
+ - save_cache:
35
+ paths:
36
+ - ./vendor/bundle
37
+ key: v1-dependencies-{{ checksum "Gemfile.lock" }}-{{ checksum "Appraisals" }}
38
+ - run: bundle exec appraisal rspec --format progress
39
+
40
+ "ruby-2.6":
41
+ docker:
42
+ - image: ruby:2.6
43
+ - image: amazon/dynamodb-local
44
+ environment:
45
+ MAX_HEAP_SIZE: 1024m
46
+ HEAP_NEWSIZE: 512m
47
+
48
+ steps:
49
+ - checkout
50
+ - restore_cache:
51
+ keys:
52
+ - v1-dependencies-{{ checksum "Gemfile.lock" }}-{{ checksum "Appraisals" }}
53
+ # fallback to using the latest cache if no exact match is found
54
+ - v1-dependencies-
55
+
56
+ - run:
57
+ name: install dependencies
58
+ command: |
59
+ gem update bundler
60
+ bundle install
61
+ bundle exec appraisal install
62
+ - save_cache:
63
+ paths:
64
+ - ./vendor/bundle
65
+ key: v1-dependencies-{{ checksum "Gemfile.lock" }}-{{ checksum "Appraisals" }}
66
+ - run: bundle exec appraisal rspec --format progress
67
+
68
+ "ruby-2.7":
69
+ docker:
70
+ - image: ruby:2.7
71
+ - image: amazon/dynamodb-local
72
+ environment:
73
+ MAX_HEAP_SIZE: 1024m
74
+ HEAP_NEWSIZE: 512m
75
+
76
+ steps:
77
+ - checkout
78
+ - restore_cache:
79
+ keys:
80
+ - v1-dependencies-{{ checksum "Gemfile.lock" }}-{{ checksum "Appraisals" }}
81
+ # fallback to using the latest cache if no exact match is found
82
+ - v1-dependencies-
83
+
84
+ - run:
85
+ name: install dependencies
86
+ command: |
87
+ gem update bundler
88
+ bundle install
89
+ bundle exec appraisal install
90
+ - save_cache:
91
+ paths:
92
+ - ./vendor/bundle
93
+ key: v1-dependencies-{{ checksum "Gemfile.lock" }}-{{ checksum "Appraisals" }}
94
+ - run: bundle exec appraisal rspec --format progress
95
+
96
+
97
+
98
+
99
+
100
+
@@ -0,0 +1,2 @@
1
+ coverage
2
+ .rspec_status
@@ -0,0 +1,8 @@
1
+
2
+ appraise "dynamoid-3.4" do
3
+ gem "dynamoid", "~> 3.4.0"
4
+ end
5
+
6
+ appraise "dynamoid-latest" do
7
+ gem "dynamoid", "~> 3.0"
8
+ end
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in dynamoid_lockable.gemspec
6
+ gemspec
7
+
8
+ gem 'pry'
9
+
@@ -0,0 +1,97 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ dynamoid_lockable (1.0.0)
5
+ dynamoid_advanced_where (>= 1.0.1, < 2.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activemodel (6.0.2.1)
11
+ activesupport (= 6.0.2.1)
12
+ activesupport (6.0.2.1)
13
+ concurrent-ruby (~> 1.0, >= 1.0.2)
14
+ i18n (>= 0.7, < 2)
15
+ minitest (~> 5.1)
16
+ tzinfo (~> 1.1)
17
+ zeitwerk (~> 2.2)
18
+ appraisal (2.2.0)
19
+ bundler
20
+ rake
21
+ thor (>= 0.14.0)
22
+ aws-eventstream (1.0.3)
23
+ aws-partitions (1.270.0)
24
+ aws-sdk-core (3.89.1)
25
+ aws-eventstream (~> 1.0, >= 1.0.2)
26
+ aws-partitions (~> 1, >= 1.239.0)
27
+ aws-sigv4 (~> 1.1)
28
+ jmespath (~> 1.0)
29
+ aws-sdk-dynamodb (1.41.0)
30
+ aws-sdk-core (~> 3, >= 3.71.0)
31
+ aws-sigv4 (~> 1.1)
32
+ aws-sigv4 (1.1.0)
33
+ aws-eventstream (~> 1.0, >= 1.0.2)
34
+ codecov (0.1.16)
35
+ json
36
+ simplecov
37
+ url
38
+ coderay (1.1.2)
39
+ concurrent-ruby (1.1.5)
40
+ diff-lcs (1.3)
41
+ docile (1.3.2)
42
+ dynamoid (3.4.1)
43
+ activemodel (>= 4)
44
+ aws-sdk-dynamodb (~> 1)
45
+ concurrent-ruby (>= 1.0)
46
+ null-logger
47
+ dynamoid_advanced_where (1.0.1)
48
+ dynamoid (>= 3.2, < 4)
49
+ i18n (1.8.2)
50
+ concurrent-ruby (~> 1.0)
51
+ jmespath (1.4.0)
52
+ json (2.3.0)
53
+ method_source (0.9.2)
54
+ minitest (5.14.0)
55
+ null-logger (0.1.5)
56
+ pry (0.12.2)
57
+ coderay (~> 1.1.0)
58
+ method_source (~> 0.9.0)
59
+ rake (12.3.3)
60
+ rspec (3.9.0)
61
+ rspec-core (~> 3.9.0)
62
+ rspec-expectations (~> 3.9.0)
63
+ rspec-mocks (~> 3.9.0)
64
+ rspec-core (3.9.1)
65
+ rspec-support (~> 3.9.1)
66
+ rspec-expectations (3.9.0)
67
+ diff-lcs (>= 1.2.0, < 2.0)
68
+ rspec-support (~> 3.9.0)
69
+ rspec-mocks (3.9.1)
70
+ diff-lcs (>= 1.2.0, < 2.0)
71
+ rspec-support (~> 3.9.0)
72
+ rspec-support (3.9.2)
73
+ simplecov (0.18.1)
74
+ docile (~> 1.1)
75
+ simplecov-html (~> 0.11.0)
76
+ simplecov-html (0.11.0)
77
+ thor (1.0.1)
78
+ thread_safe (0.3.6)
79
+ tzinfo (1.2.6)
80
+ thread_safe (~> 0.1)
81
+ url (0.3.2)
82
+ zeitwerk (2.2.2)
83
+
84
+ PLATFORMS
85
+ ruby
86
+ x86_64-darwin-18
87
+
88
+ DEPENDENCIES
89
+ appraisal
90
+ codecov
91
+ dynamoid_lockable!
92
+ pry
93
+ rake (~> 12)
94
+ rspec (~> 3)
95
+
96
+ BUNDLED WITH
97
+ 2.1.4
@@ -0,0 +1,79 @@
1
+ # DynamoidLockable
2
+ `DynamoidLockable` provides a interface for pessimistic locking for Dynamoid models.
3
+
4
+ ## Installation
5
+
6
+ Add this line to your application's Gemfile:
7
+
8
+ ```ruby
9
+ gem 'dynamoid_lockable'
10
+ ```
11
+
12
+ And then execute:
13
+
14
+ $ bundle install
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install dynamoid_lockable
19
+
20
+ ## Usage
21
+ ```
22
+ class MyModel
23
+ include Dynamoid::Document
24
+ include DynamoidLockable
25
+
26
+ # Models may have more than one lock discriminated by name.
27
+ # Creates a lock named :importing, which locks by default for 5 minutes and will refresh the lock every 10 seconds.
28
+ # Default values:
29
+ # lock_for: 10 minutes
30
+ # relock_every: 1/3 of lock_for
31
+ locks_with :importing, lock_for: 5 * 60, relock_every: 10
32
+ end
33
+
34
+ thing = MyModel.find('abcd')
35
+
36
+ # Most basic usage
37
+ thing.perform_with_lock(:importing) do
38
+ # do whatever you like in here, record will remain locked as long as this block is executing and the
39
+ # thread is able to do work
40
+ end
41
+
42
+ # Manually locking and unlocking
43
+ thing.lock(:importing)
44
+ # WARNING! Lock is not persisted, you have 5 minutes to complete your task
45
+ do_work(thing)
46
+ thing.unlock(:importing)
47
+
48
+ # You can also search by entities which are lockable (unlocked, locks expired or eleigible for reentry)
49
+ search = MyModel.lockable(:importing).first
50
+
51
+ # Note that `lockable` returns a dynamoid_advanced_where chain, so additional where clauses may be appended
52
+
53
+ # Locks support reentry from the same thread
54
+ thing.lock(:importing)
55
+ thing.lock(:importing) # No error
56
+
57
+ Thread.new do
58
+ thing.lock(:importing) # Error: Concerns::Lockable::CouldNotAcquireLock
59
+ end
60
+ ```
61
+
62
+ A note on the options:
63
+ * *locks_for* - should be a reasonably high number, setting it too small will result in excessive DynamoDB usage
64
+ * *relock_every* - should be a positive number, nil or 0 will result in the relock process being disabled
65
+
66
+ ## Errors
67
+ * If unable to obtain a lock, `DynamoidLockable::CouldNotAcquireLock` is raised
68
+ * If unable to obtain to release a lock, `DynamoidLockable::CouldNotUnlock` is raised
69
+
70
+ ## Development
71
+
72
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
73
+
74
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
75
+
76
+ ## Contributing
77
+
78
+ Bug reports and pull requests are welcome on GitHub at https://github.com/GetTerminus/dynamoid_lockable.
79
+
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "dynamoid_lockable"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/dynamoid_lockable/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'dynamoid_lockable'
7
+ spec.version = DynamoidLockable::VERSION
8
+ spec.authors = ['Brian Malinconico']
9
+ spec.email = ['bmalinconico@terminus.com']
10
+
11
+ spec.summary = 'Lock your Dynamoid records for only your use'
12
+ spec.description = 'Uses atomic writes to DynamoDB to ensure you hold an exclusive lock on your records'
13
+ spec.homepage = 'https://github.com/GetTerminus/dynamoid_lockable'
14
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
15
+
16
+ spec.metadata['allowed_push_host'] = 'https://www.rubygems.org'
17
+
18
+ spec.metadata['homepage_uri'] = spec.homepage
19
+ spec.metadata['source_code_uri'] = 'https://github.com/GetTerminus/dynamoid_lockable'
20
+ spec.metadata['changelog_uri'] = 'https://github.com/GetTerminus/dynamoid_lockable/blob/master/CHANGELOG.md'
21
+
22
+ # Specify which files should be added to the gem when it is released.
23
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
25
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
+ end
27
+ spec.bindir = 'exe'
28
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ['lib']
30
+
31
+ spec.add_runtime_dependency 'dynamoid_advanced_where', '>= 1.0.1', '< 2.0'
32
+
33
+ spec.add_development_dependency 'appraisal'
34
+ spec.add_development_dependency 'rake', '~> 12'
35
+ spec.add_development_dependency 'rspec', '~> 3'
36
+ spec.add_development_dependency 'codecov'
37
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dynamoid_lockable/version'
4
+ require 'dynamoid_advanced_where'
5
+
6
+ require 'securerandom'
7
+
8
+ module DynamoidLockable
9
+ class Error < StandardError; end
10
+
11
+ class LockingError < Error; end
12
+ class CouldNotAcquireLock < LockingError; end
13
+ class CouldNotUnlock < LockingError; end
14
+
15
+ DEFAULT_LOCK_TIME = 15 * 60
16
+
17
+ def lock(name, locker_name: self.class.locking_name)
18
+ ensure_lockable_field(name)
19
+
20
+ locked_until = Time.now + self.class.lock_config(name)[:duration]
21
+
22
+ result = self.class
23
+ .lockable(name, locker_name: locker_name)
24
+ .upsert(
25
+ hash_key,
26
+ "#{name}_locked_until": locked_until,
27
+ "#{name}_locked_by": locker_name
28
+ )
29
+
30
+ raise CouldNotAcquireLock unless result
31
+
32
+ # Prevents having to call reload, which we've seen take 20seconds
33
+ # but still populates the value in memory, in case the code block
34
+ # calls save and persists this copy again
35
+ send("#{name}_locked_until=", locked_until)
36
+ send("#{name}_locked_by=", locker_name)
37
+
38
+ true
39
+ end
40
+
41
+ def unlock(name)
42
+ ensure_lockable_field(name)
43
+
44
+ result = self.class.unlockable(name)
45
+ .upsert(hash_key, "#{name}_locked_until": nil)
46
+
47
+ raise CouldNotUnlock unless result
48
+
49
+ true
50
+ end
51
+
52
+ def create_relock_thread(name)
53
+ locker_name = self.class.locking_name
54
+
55
+ Thread.new do
56
+ config = self.class.lock_config(name)
57
+ loop do
58
+ lock(name, locker_name: locker_name)
59
+
60
+ sleep config[:relock_every]
61
+ end
62
+ end
63
+ end
64
+
65
+ def perform_with_lock(name)
66
+ ensure_lockable_field(name)
67
+ config = self.class.lock_config(name)
68
+
69
+ result = lock(name)
70
+
71
+ if config[:relock_every]&.positive?
72
+ relock_thread = create_relock_thread(name)
73
+ end
74
+
75
+ yield
76
+ ensure
77
+ relock_thread&.exit
78
+ unlock(name) if result # Don't try to unlock if you didn't acquire lock
79
+ end
80
+
81
+ def ensure_lockable_field(name)
82
+ self.class.ensure_lockable_field(name)
83
+ end
84
+
85
+ module ClassMethods
86
+ def locking_name
87
+ Thread.current[:dynamo_lock_name] ||= SecureRandom.uuid
88
+ end
89
+
90
+ # TODO: Make this inheritance safe
91
+ def lockable_fields
92
+ @lockable_fields ||= {}
93
+ end
94
+
95
+ def lockable_fields=(val)
96
+ @lockable_fields = val
97
+ end
98
+
99
+ def lock_config(name)
100
+ lockable_fields[name.to_sym]
101
+ end
102
+
103
+ def locks_with(base_field, lock_for: DEFAULT_LOCK_TIME, relock_every: lock_for / 3)
104
+ self.lockable_fields = lockable_fields.merge(
105
+ base_field.to_sym => { duration: lock_for, relock_every: relock_every }
106
+ )
107
+
108
+ field "#{base_field}_locked_until".to_sym, :datetime
109
+ field "#{base_field}_locked_by".to_sym, :string
110
+ end
111
+
112
+ def lockable(lock_name, locker_name: locking_name)
113
+ ensure_lockable_field(lock_name)
114
+
115
+ advanced_where do |r|
116
+ r.send(hash_key).exists? & (
117
+ (r.send("#{lock_name}_locked_by") == locker_name) |
118
+ !r.send("#{lock_name}_locked_until").exists? |
119
+ (r.send("#{lock_name}_locked_until") < Time.now)
120
+ )
121
+ end
122
+ end
123
+
124
+ def ensure_lockable_field(name)
125
+ raise "Lock #{name} unrecognized" unless lockable_fields.key?(name)
126
+ end
127
+
128
+ def unlockable(lock_name, locker_name: locking_name)
129
+ ensure_lockable_field(lock_name)
130
+
131
+ advanced_where do |r|
132
+ r.send(self.hash_key).exists? &
133
+ (r.send("#{lock_name}_locked_by") == locker_name)
134
+ end
135
+ end
136
+ end
137
+
138
+ def self.included(other)
139
+ other.extend(ClassMethods)
140
+ end
141
+ end
@@ -0,0 +1,3 @@
1
+ module DynamoidLockable
2
+ VERSION = "1.0.0"
3
+ end
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dynamoid_lockable
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Brian Malinconico
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-02-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dynamoid_advanced_where
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.1
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '2.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 1.0.1
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: appraisal
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '12'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '12'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rspec
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '3'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '3'
75
+ - !ruby/object:Gem::Dependency
76
+ name: codecov
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ description: Uses atomic writes to DynamoDB to ensure you hold an exclusive lock on
90
+ your records
91
+ email:
92
+ - bmalinconico@terminus.com
93
+ executables: []
94
+ extensions: []
95
+ extra_rdoc_files: []
96
+ files:
97
+ - ".circleci/config.yml"
98
+ - ".gitignore"
99
+ - Appraisals
100
+ - Gemfile
101
+ - Gemfile.lock
102
+ - README.md
103
+ - Rakefile
104
+ - bin/console
105
+ - bin/setup
106
+ - dynamoid_lockable.gemspec
107
+ - lib/dynamoid_lockable.rb
108
+ - lib/dynamoid_lockable/version.rb
109
+ homepage: https://github.com/GetTerminus/dynamoid_lockable
110
+ licenses: []
111
+ metadata:
112
+ allowed_push_host: https://www.rubygems.org
113
+ homepage_uri: https://github.com/GetTerminus/dynamoid_lockable
114
+ source_code_uri: https://github.com/GetTerminus/dynamoid_lockable
115
+ changelog_uri: https://github.com/GetTerminus/dynamoid_lockable/blob/master/CHANGELOG.md
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: 2.5.0
125
+ required_rubygems_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ requirements: []
131
+ rubygems_version: 3.0.1
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: Lock your Dynamoid records for only your use
135
+ test_files: []