momento 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,80 @@
1
+ require 'grpc'
2
+ require 'momento/cacheclient_pb'
3
+
4
+ module Momento
5
+ # Responses specific to get.
6
+ class GetResponse < Response
7
+ class << self
8
+ # Build a Momento::GetResponse from a block of code
9
+ # which returns a Momento::ControlClient::GetResponse.
10
+ #
11
+ # @return [Momento::GetResponse]
12
+ # @raise [StandardError] when the exception is not recognized.
13
+ # @raise [TypeError] when the response is not recognized.
14
+ def from_block
15
+ response = yield
16
+ rescue GRPC::BadStatus => e
17
+ Error.new(grpc_exception: e)
18
+ else
19
+ from_response(response)
20
+ end
21
+
22
+ private
23
+
24
+ def from_response(response)
25
+ raise TypeError unless response.is_a?(Momento::CacheClient::GetResponse)
26
+
27
+ case response.result
28
+ when :Hit
29
+ Hit.new(grpc_response: response)
30
+ when :Miss
31
+ Miss.new
32
+ else
33
+ raise "Unknown get result: #{response.result}"
34
+ end
35
+ end
36
+ end
37
+
38
+ def hit?
39
+ false
40
+ end
41
+
42
+ def miss?
43
+ false
44
+ end
45
+
46
+ # Successfully got an item from the cache.
47
+ class Hit < GetResponse
48
+ # rubocop:disable Lint/MissingSuper
49
+ def initialize(grpc_response:)
50
+ @grpc_response = grpc_response
51
+ end
52
+ # rubocop:enable Lint/MissingSuper
53
+
54
+ def hit?
55
+ true
56
+ end
57
+
58
+ # @return [String] the value from the cache
59
+ def value
60
+ @grpc_response.cache_body
61
+ end
62
+
63
+ def to_s
64
+ value
65
+ end
66
+ end
67
+
68
+ # The key had no value stored in the cache.
69
+ class Miss < GetResponse
70
+ def miss?
71
+ true
72
+ end
73
+ end
74
+
75
+ # There was a problem getting the value from the cache.
76
+ class Error < GetResponse
77
+ include Momento::Response::Error
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,53 @@
1
+ require 'grpc'
2
+ require 'momento/controlclient_pb'
3
+
4
+ module Momento
5
+ # Responses specific to list_caches.
6
+ class ListCachesResponse < Response
7
+ # Build a Momento::ListCachesResponse from a block of code
8
+ # which returns a Momento::ControlClient::ListCachesResponse.
9
+ #
10
+ # @return [Momento::ListCachesResponse]
11
+ # @raise [StandardError] when the exception is not recognized.
12
+ # @raise [TypeError] when the response is not recognized.
13
+ def self.from_block
14
+ response = yield
15
+ rescue GRPC::BadStatus => e
16
+ Error.new(grpc_exception: e)
17
+ else
18
+ raise TypeError unless response.is_a?(Momento::ControlClient::ListCachesResponse)
19
+
20
+ return Success.new(grpc_response: response)
21
+ end
22
+
23
+ def success?
24
+ false
25
+ end
26
+
27
+ # A Momento resposne with a page of caches.
28
+ class Success < ListCachesResponse
29
+ # rubocop:disable Lint/MissingSuper
30
+ def initialize(grpc_response:)
31
+ @grpc_response = grpc_response
32
+ end
33
+ # rubocop:enable Lint/MissingSuper
34
+
35
+ def success?
36
+ true
37
+ end
38
+
39
+ def cache_names
40
+ @grpc_response.cache.map(&:cache_name)
41
+ end
42
+
43
+ def next_token
44
+ @grpc_response.next_token
45
+ end
46
+ end
47
+
48
+ # There was an error listing the caches.
49
+ class Error < ListCachesResponse
50
+ include ::Momento::Response::Error
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,16 @@
1
+ module Momento
2
+ class Response
3
+ # A module for responses which contain errors.
4
+ module Error
5
+ attr_accessor :grpc_exception
6
+
7
+ def initialize(grpc_exception:)
8
+ @grpc_exception = grpc_exception
9
+ end
10
+
11
+ def error?
12
+ true
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ require 'grpc'
2
+ require_relative 'response/error'
3
+ require_relative 'create_cache_response'
4
+ require_relative 'delete_response'
5
+ require_relative 'delete_cache_response'
6
+ require_relative 'get_response'
7
+ require_relative 'list_caches_response'
8
+ require_relative 'set_response'
9
+
10
+ module Momento
11
+ # A superclass for all Momento responses.
12
+ class Response
13
+ def error?
14
+ false
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,39 @@
1
+ require 'grpc'
2
+ require 'momento/cacheclient_pb'
3
+
4
+ module Momento
5
+ # Responses specific to set.
6
+ class SetResponse < Response
7
+ # Build a Momento::SetResponse from a block of code
8
+ # which returns a Momento::ControlClient::SetResponse.
9
+ #
10
+ # @return [Momento::SetResponse]
11
+ # @raise [StandardError] when the exception is not recognized.
12
+ # @raise [TypeError] when the response is not recognized.
13
+ def self.from_block
14
+ response = yield
15
+ rescue GRPC::BadStatus => e
16
+ Error.new(grpc_exception: e)
17
+ else
18
+ raise TypeError unless response.is_a?(Momento::CacheClient::SetResponse)
19
+
20
+ Success.new
21
+ end
22
+
23
+ def success?
24
+ false
25
+ end
26
+
27
+ # The item was set.
28
+ class Success < SetResponse
29
+ def success?
30
+ true
31
+ end
32
+ end
33
+
34
+ # There was an error setting the item.
35
+ class Error < SetResponse
36
+ include Momento::Response::Error
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,204 @@
1
+ require 'jwt'
2
+ require 'momento/cacheclient_services_pb'
3
+ require 'momento/controlclient_services_pb'
4
+ require 'momento/response'
5
+
6
+ module Momento
7
+ # A simple client for Momento.
8
+ #
9
+ # @example
10
+ # client = Momento::SimpleCacheClient.new(
11
+ # auth_token: jwt,
12
+ # default_ttl: 10_000
13
+ # )
14
+ #
15
+ # response = client.get("my_cache", "key")
16
+ # if response.hit?
17
+ # puts "We got #{response}"
18
+ # elsif response.miss?
19
+ # puts "It's not in the cache"
20
+ # elsif response.error?
21
+ # puts "The front fell off."
22
+ # end
23
+ class SimpleCacheClient
24
+ VERSION = Momento::VERSION
25
+ CACHE_CLIENT_STUB_CLASS = CacheClient::Scs::Stub
26
+ CONTROL_CLIENT_STUB_CLASS = ControlClient::ScsControl::Stub
27
+
28
+ # The default time to live, in milliseconds.
29
+ attr_accessor :default_ttl
30
+
31
+ # @param auth_token [String] the JWT for your Momento account
32
+ # @param default_ttl [Integer]
33
+ def initialize(auth_token:, default_ttl:)
34
+ @auth_token = auth_token
35
+ @default_ttl = default_ttl
36
+ load_endpoints_from_token
37
+ end
38
+
39
+ # Get a value in a cache.
40
+ #
41
+ # Momento only stores bytes; the returned value will be encoded as ASCII-8BIT.
42
+ #
43
+ # @param cache_name [String]
44
+ # @param key [String] must only contain ASCII characters
45
+ # @return [Momento::GetResponse]
46
+ def get(cache_name, key)
47
+ return GetResponse.from_block do
48
+ cache_stub.get(
49
+ CacheClient::GetRequest.new(cache_key: to_bytes(key)),
50
+ metadata: { cache: cache_name }
51
+ )
52
+ end
53
+ end
54
+
55
+ # Set a value in a cache.
56
+ #
57
+ # If ttl is not set, it will use the default_ttl.
58
+ #
59
+ # @param cache_name [String]
60
+ # @param key [String] must only contain ASCII characters
61
+ # @param value [String] the value to cache
62
+ # @param ttl [Integer] time to live, in milliseconds.
63
+ # @return [Momento::SetResponse]
64
+ def set(cache_name, key, value, ttl: default_ttl)
65
+ return SetResponse.from_block do
66
+ req = CacheClient::SetRequest.new(
67
+ cache_key: to_bytes(key),
68
+ cache_body: to_bytes(value),
69
+ ttl_milliseconds: ttl
70
+ )
71
+
72
+ cache_stub.set(req, metadata: { cache: cache_name })
73
+ end
74
+ end
75
+
76
+ # Delete a key in a cache.
77
+ #
78
+ # @param cache_name [String]
79
+ # @param key [String] must only contain ASCII characters
80
+ # @return [Momento::DeleteResponse]
81
+ def delete(cache_name, key)
82
+ return DeleteResponse.from_block do
83
+ cache_stub.delete(
84
+ CacheClient::DeleteRequest.new(cache_key: to_bytes(key)),
85
+ metadata: { cache: cache_name }
86
+ )
87
+ end
88
+ end
89
+
90
+ # Create a new Momento cache.
91
+ #
92
+ # @param name [String] the name of the cache to create.
93
+ # @return [Momento::CreateCacheResponse] the response from Momento.
94
+ def create_cache(name)
95
+ return CreateCacheResponse.from_block do
96
+ control_stub.create_cache(
97
+ ControlClient::CreateCacheRequest.new(cache_name: name)
98
+ )
99
+ end
100
+ end
101
+
102
+ # Delete an existing Momento cache.
103
+ #
104
+ # @param name [String] the name of the cache to delete.
105
+ # @return [Momento::DeleteCacheResponse] the response from Momento.
106
+ def delete_cache(name)
107
+ return DeleteCacheResponse.from_block do
108
+ control_stub.delete_cache(
109
+ ControlClient::DeleteCacheRequest.new(cache_name: name)
110
+ )
111
+ end
112
+ end
113
+
114
+ # List a page of your caches.
115
+ #
116
+ # The next_token indicates which page to fetch.
117
+ # If nil or "" it will fetch the first page. Default is to fetch the first page.
118
+ #
119
+ # @params next_token [String, nil] the token of the page to request
120
+ # @return [Momento::ListCachesResponse]
121
+ def list_caches(next_token: "")
122
+ return ListCachesResponse.from_block do
123
+ control_stub.list_caches(
124
+ ControlClient::ListCachesRequest.new(next_token: next_token)
125
+ )
126
+ end
127
+ end
128
+
129
+ # Lists the names of all your caches.
130
+ #
131
+ # @return [Enumerator::Lazy<String>] the cache names
132
+ # @raise [GRPC::BadStatus]
133
+ # rubocop:disable Metrics/MethodLength
134
+ def caches
135
+ Enumerator.new do |yielder|
136
+ next_token = ""
137
+
138
+ loop do
139
+ response = list_caches(next_token: next_token)
140
+ raise response.grpc_exception if response.is_a? Momento::Response::Error
141
+
142
+ response.cache_names.each do |name|
143
+ yielder << name
144
+ end
145
+
146
+ break if response.next_token == ''
147
+
148
+ next_token = response.next_token
149
+ end
150
+ end.lazy
151
+ end
152
+ # rubocop:enable Metrics/MethodLength
153
+
154
+ private
155
+
156
+ def cache_stub
157
+ @cache_stub ||= CACHE_CLIENT_STUB_CLASS.new(@cache_endpoint, combined_credentials)
158
+ end
159
+
160
+ def control_stub
161
+ @control_stub ||= CONTROL_CLIENT_STUB_CLASS.new(@control_endpoint, combined_credentials)
162
+ end
163
+
164
+ def combined_credentials
165
+ @combined_credentials ||= make_combined_credentials
166
+ end
167
+
168
+ def load_endpoints_from_token
169
+ claim = JWT.decode(@auth_token, nil, false).first
170
+
171
+ @control_endpoint = claim["cp"]
172
+ @cache_endpoint = claim["c"]
173
+ end
174
+
175
+ def make_combined_credentials
176
+ # :nocov:
177
+ auth_proc = proc do
178
+ { authorization: @auth_token, agent: "ruby:#{VERSION}" }
179
+ end
180
+ # :nocov:
181
+
182
+ call_creds = GRPC::Core::CallCredentials.new(auth_proc)
183
+
184
+ return GRPC::Core::ChannelCredentials.new.compose(call_creds)
185
+ end
186
+
187
+ # Ruby uses String for bytes. GRPC wants a String encoded as ASCII.
188
+ # GRPC will re-encode a String, but treats it as characters; GRPC will
189
+ # raise if you pass a String with non-ASCII characters.
190
+ # So we do the re-encoding ourselves in a way that treats the String as
191
+ # bytes and will not raise. The data is not changed.
192
+ #
193
+ # A duplicate String is returned, but since Ruby is copy-on-write it
194
+ # does not copy the data.
195
+ #
196
+ # @param string [String] the string to make safe for GRPC bytes
197
+ # @return [String] a duplicate safe to use as GRPC bytes
198
+ def to_bytes(string)
199
+ # dup in case the value is frozen and to avoid changing the value's encoding
200
+ # for the caller.
201
+ return string.dup.force_encoding(Encoding::ASCII_8BIT)
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Momento
4
+ VERSION = "0.1.0"
5
+ end
data/lib/momento.rb ADDED
@@ -0,0 +1,6 @@
1
+ require_relative 'momento/version'
2
+ require_relative 'momento/simple_cache_client'
3
+
4
+ # Top level namespace for all Momento code.
5
+ module Momento
6
+ end
data/momento.gemspec ADDED
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/momento/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "momento"
7
+ spec.version = Momento::VERSION
8
+ spec.authors = ["Momento"]
9
+ spec.email = ["eng-deveco@momentohq.com"]
10
+
11
+ spec.summary = "Client for Momento Serverless Cache"
12
+ spec.description = "Momento is a fast, simple, pay-as-you-go caching solution."
13
+ spec.homepage = "https://github.com/momentohq/client-sdk-ruby"
14
+ spec.required_ruby_version = ">= 2.7.0"
15
+ spec.license = "Apache-2.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata['rubygems_mfa_required'] = 'true'
19
+ spec.metadata["source_code_uri"] = "https://github.com/momentohq/client-sdk-ruby"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(__dir__) do
24
+ `git ls-files -z`.split("\x0").reject do |f|
25
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
26
+ end
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.add_development_dependency 'bundler', "~> 2.3"
33
+ spec.add_development_dependency 'factory_bot', "~> 6.2.1"
34
+ spec.add_development_dependency 'faker', "~> 3.0"
35
+ spec.add_development_dependency "rspec", "~> 3.12"
36
+ spec.add_development_dependency "rubocop", "~> 3.18"
37
+ spec.add_development_dependency 'rubocop-performance', '~> 1.15'
38
+ spec.add_development_dependency 'rubocop-rake', '~> 0.6.0'
39
+ spec.add_development_dependency 'rubocop-rspec', '~> 2.15'
40
+ spec.add_development_dependency 'simplecov', '~> 0.21'
41
+
42
+ spec.add_dependency "grpc", '~> 1'
43
+ spec.add_dependency 'jwt', '~> 2'
44
+ end
@@ -0,0 +1,6 @@
1
+ module Momento
2
+ module Client
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,229 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: momento
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Momento
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-11-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: factory_bot
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 6.2.1
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 6.2.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: faker
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.12'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.12'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.18'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.18'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-performance
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.15'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.15'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop-rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.6.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.6.0
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop-rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '2.15'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '2.15'
125
+ - !ruby/object:Gem::Dependency
126
+ name: simplecov
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.21'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.21'
139
+ - !ruby/object:Gem::Dependency
140
+ name: grpc
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '1'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '1'
153
+ - !ruby/object:Gem::Dependency
154
+ name: jwt
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '2'
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '2'
167
+ description: Momento is a fast, simple, pay-as-you-go caching solution.
168
+ email:
169
+ - eng-deveco@momentohq.com
170
+ executables: []
171
+ extensions: []
172
+ extra_rdoc_files: []
173
+ files:
174
+ - ".editorconfig"
175
+ - ".rspec"
176
+ - ".rubocop.yml"
177
+ - ".ruby-version"
178
+ - CONTRIBUTING.md
179
+ - Gemfile
180
+ - Gemfile.lock
181
+ - LICENSE.txt
182
+ - README.md
183
+ - Rakefile
184
+ - examples/basic.rb
185
+ - lib/README-generating-pb.txt
186
+ - lib/momento.rb
187
+ - lib/momento/cacheclient_pb.rb
188
+ - lib/momento/cacheclient_services_pb.rb
189
+ - lib/momento/controlclient_pb.rb
190
+ - lib/momento/controlclient_services_pb.rb
191
+ - lib/momento/create_cache_response.rb
192
+ - lib/momento/delete_cache_response.rb
193
+ - lib/momento/delete_response.rb
194
+ - lib/momento/get_response.rb
195
+ - lib/momento/list_caches_response.rb
196
+ - lib/momento/response.rb
197
+ - lib/momento/response/error.rb
198
+ - lib/momento/set_response.rb
199
+ - lib/momento/simple_cache_client.rb
200
+ - lib/momento/version.rb
201
+ - momento.gemspec
202
+ - sig/momento/client.rbs
203
+ homepage: https://github.com/momentohq/client-sdk-ruby
204
+ licenses:
205
+ - Apache-2.0
206
+ metadata:
207
+ homepage_uri: https://github.com/momentohq/client-sdk-ruby
208
+ rubygems_mfa_required: 'true'
209
+ source_code_uri: https://github.com/momentohq/client-sdk-ruby
210
+ post_install_message:
211
+ rdoc_options: []
212
+ require_paths:
213
+ - lib
214
+ required_ruby_version: !ruby/object:Gem::Requirement
215
+ requirements:
216
+ - - ">="
217
+ - !ruby/object:Gem::Version
218
+ version: 2.7.0
219
+ required_rubygems_version: !ruby/object:Gem::Requirement
220
+ requirements:
221
+ - - ">="
222
+ - !ruby/object:Gem::Version
223
+ version: '0'
224
+ requirements: []
225
+ rubygems_version: 3.3.7
226
+ signing_key:
227
+ specification_version: 4
228
+ summary: Client for Momento Serverless Cache
229
+ test_files: []