From 9fdf5298b59030da0fd484e9c1fa2fd7fb7a1992 Mon Sep 17 00:00:00 2001 From: gautamomento Date: Mon, 1 Nov 2021 21:13:28 -0700 Subject: [PATCH 01/11] feat: Base implementation for Cache Operations --- .gitignore | 2 + setup.cfg | 4 +- src/momento_sdk/authorization_interceptor.py | 4 ++ src/momento_sdk/cache.py | 55 +++++++++++++++++++ src/momento_sdk/cache_name_interceptor.py | 4 ++ src/momento_sdk/generic_client_interceptor.py | 16 ++++++ src/momento_sdk/header_client_interceptor.py | 31 +++++++++++ src/momento_sdk/momento.py | 33 +++++++++-- src/momento_sdk/momento_endpoint_resolver.py | 28 ++++++++++ 9 files changed, 172 insertions(+), 5 deletions(-) create mode 100644 src/momento_sdk/authorization_interceptor.py create mode 100644 src/momento_sdk/cache.py create mode 100644 src/momento_sdk/cache_name_interceptor.py create mode 100644 src/momento_sdk/generic_client_interceptor.py create mode 100644 src/momento_sdk/header_client_interceptor.py create mode 100644 src/momento_sdk/momento_endpoint_resolver.py diff --git a/.gitignore b/.gitignore index d93d06ff..a2773a6e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ # Compiled python modules. src/*.pyc +src/momento_sdk/*.pyc +src/momento_sdk/__pycache__ # Setuptools distribution folder. /dist/ diff --git a/setup.cfg b/setup.cfg index b0d073bd..259d432e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,9 +7,11 @@ package_dir = packages = find: python_requires = >=3.6 install_requires = - momento-wire-types==0.3.0 + momento-wire-types==0.6.0 build setuptools + pyjwt + grpcio [options.packages.find] where = src diff --git a/src/momento_sdk/authorization_interceptor.py b/src/momento_sdk/authorization_interceptor.py new file mode 100644 index 00000000..fa47107f --- /dev/null +++ b/src/momento_sdk/authorization_interceptor.py @@ -0,0 +1,4 @@ +from . import header_client_interceptor + +def get_authorization_interceptor(auth_token): + return header_client_interceptor.header_adder_interceptor('authorization', auth_token) diff --git a/src/momento_sdk/cache.py b/src/momento_sdk/cache.py new file mode 100644 index 00000000..55b8e922 --- /dev/null +++ b/src/momento_sdk/cache.py @@ -0,0 +1,55 @@ +import grpc +from . import authorization_interceptor +from . import cache_name_interceptor +import momento_wire_types.cacheclient_pb2_grpc as cache_client +import momento_wire_types.cacheclient_pb2 as cache_client_types + +class Cache: + + def __init__(self, auth_token, cache_name, endpoint, default_ttlSeconds): + self._default_ttlSeconds = default_ttlSeconds + self._secure_channel = grpc.secure_channel(endpoint, grpc.ssl_channel_credentials()) + auth_interceptor = authorization_interceptor.get_authorization_interceptor(auth_token=auth_token) + cache_interceptor = cache_name_interceptor.get_cache_name_interceptor(cache_name=cache_name) + intercept_channel = grpc.intercept_channel(self._secure_channel, auth_interceptor, cache_interceptor) + self._client = cache_client.ScsStub(channel=intercept_channel) + # self._wait_till_ready() + + # Temporary hack + # TODO: Make this time bound + def _wait_till_ready(self): + while(True): + try: + self._client.Get('b\0x01') + break + except: + True + + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, exc_traceback): + self._secure_channel.close() + + + def set(self, key , value, ttl_seconds=None): + item_ttl_seconds = self._default_ttlSeconds if ttl_seconds == None else ttl_seconds + set_request = cache_client_types.SetRequest() + set_request.cache_key = self._asBytes(key, "Unsupported type for key: ") + set_request.cache_body = self._asBytes(value, "Unsupported type for value: ") + set_request.ttl_milliseconds = item_ttl_seconds * 1000 + self._client.Set(set_request) + + def get(self, key): + get_request = cache_client_types.GetRequest() + get_request.cache_key = self._asBytes(key, "Unsupported type for key: ") + response = self._client.Get(get_request) + print(response) + + def _asBytes(self, data, errorMessage): + if (isinstance(data, str)): + return data.encode('utf-8') + if (isinstance(data, bytes)): + return data + raise TypeError(errorMessage + str(type(data))) diff --git a/src/momento_sdk/cache_name_interceptor.py b/src/momento_sdk/cache_name_interceptor.py new file mode 100644 index 00000000..a4d3ea5c --- /dev/null +++ b/src/momento_sdk/cache_name_interceptor.py @@ -0,0 +1,4 @@ +from . import header_client_interceptor + +def get_cache_name_interceptor(cache_name): + return header_client_interceptor.header_adder_interceptor('cache', cache_name) \ No newline at end of file diff --git a/src/momento_sdk/generic_client_interceptor.py b/src/momento_sdk/generic_client_interceptor.py new file mode 100644 index 00000000..849efe49 --- /dev/null +++ b/src/momento_sdk/generic_client_interceptor.py @@ -0,0 +1,16 @@ +import grpc + + +class _GenericClientInterceptor(grpc.UnaryUnaryClientInterceptor): + + def __init__(self, interceptor_function): + self._fn = interceptor_function + + def intercept_unary_unary(self, continuation, client_call_details, request): + new_details, new_request_iterator, postprocess = self._fn( + client_call_details, iter((request,)), False, False) + response = continuation(new_details, next(new_request_iterator)) + return postprocess(response) if postprocess else response + +def create(intercept_call): + return _GenericClientInterceptor(intercept_call) \ No newline at end of file diff --git a/src/momento_sdk/header_client_interceptor.py b/src/momento_sdk/header_client_interceptor.py new file mode 100644 index 00000000..67f1fe26 --- /dev/null +++ b/src/momento_sdk/header_client_interceptor.py @@ -0,0 +1,31 @@ +import collections + +from momento_sdk import generic_client_interceptor +import grpc + + +class _ClientCallDetails( + collections.namedtuple( + '_ClientCallDetails', + ('method', 'timeout', 'metadata', 'credentials')), + grpc.ClientCallDetails): + pass + + +def header_adder_interceptor(header, value): + + def intercept_call(client_call_details, request_iterator, request_streaming, + response_streaming): + metadata = [] + if client_call_details.metadata is not None: + metadata = list(client_call_details.metadata) + metadata.append(( + header, + value, + )) + client_call_details = _ClientCallDetails( + client_call_details.method, client_call_details.timeout, metadata, + client_call_details.credentials) + return client_call_details, request_iterator, None + + return generic_client_interceptor.create(intercept_call) diff --git a/src/momento_sdk/momento.py b/src/momento_sdk/momento.py index a4b91ba0..146a17c1 100644 --- a/src/momento_sdk/momento.py +++ b/src/momento_sdk/momento.py @@ -1,13 +1,38 @@ -import momento_wire_types.controlclient_pb2 as control_client +from momento_wire_types.controlclient_pb2 import CreateCacheRequest, DeleteCacheRequest +import momento_wire_types.controlclient_pb2_grpc as control_client +import grpc + +from momento_sdk.cache import Cache +from . import authorization_interceptor, momento_endpoint_resolver class Momento: def __init__(self, auth_token, endpoint_override=None): - self._auth_token__ = auth_token - self._endpoint_override = endpoint_override + endpoints = momento_endpoint_resolver._resolve(auth_token=auth_token, endpoint_override=endpoint_override) + self._auth_token = auth_token + self._control_endpoint = endpoints.control_endpoint + self._cache_endpoint = endpoints.cache_endpoint + self._secure_channel = grpc.secure_channel(self._control_endpoint, grpc.ssl_channel_credentials()) + intercept_channel = grpc.intercept_channel(self._secure_channel, authorization_interceptor.get_authorization_interceptor(auth_token)) + self._client = control_client.ScsControlStub(channel=intercept_channel) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, exc_traceback): + self._secure_channel.close() def create_cache(self, cache_name): - print("hello") + request = CreateCacheRequest() + request.cache_name = cache_name + self._client.CreateCache(request) + + def delete_cache(self, cache_name): + request = DeleteCacheRequest() + request.cache_name = cache_name + self._client.DeleteCache(request) + def get_cache(self, cache_name, ttl_seconds, options=None) : + return Cache(auth_token=self._auth_token, cache_name=cache_name, endpoint=self._cache_endpoint, default_ttlSeconds=ttl_seconds) def init(auth_token): return Momento(auth_token=auth_token) diff --git a/src/momento_sdk/momento_endpoint_resolver.py b/src/momento_sdk/momento_endpoint_resolver.py new file mode 100644 index 00000000..12a99bea --- /dev/null +++ b/src/momento_sdk/momento_endpoint_resolver.py @@ -0,0 +1,28 @@ +import jwt +from jwt.exceptions import DecodeError + +_MOMENTO_CONTROL_ENDPOINT_PREFIX = 'control.' +_MOMENTO_CACHE_ENDPOINT_PREFIX = 'cache.' +_CONTROL_ENDPOINT_CLAIM_ID = 'cp' +_CACHE_ENDPOINT_CLAIM_ID = 'c' + +class _Endpoints: + + def __init__(self, control_endpoint, cache_endpoint): + self.control_endpoint = control_endpoint + self.cache_endpoint = cache_endpoint + + +def _resolve(auth_token, endpoint_override=None) : + if (endpoint_override != None) : + return _Endpoints(_MOMENTO_CONTROL_ENDPOINT_PREFIX + endpoint_override, _MOMENTO_CACHE_ENDPOINT_PREFIX + endpoint_override) + return _getEndpointFromToken(auth_token) + +def _getEndpointFromToken(auth_token): + try: + claims = jwt.decode(auth_token, options={"verify_signature": False}) + return _Endpoints(claims[_CONTROL_ENDPOINT_CLAIM_ID], claims[_CACHE_ENDPOINT_CLAIM_ID]) + except DecodeError: + raise Exception + except KeyError: + raise Exception \ No newline at end of file From eb14c5a6fbf9a20b85e6db6b11cac6eace15bfb5 Mon Sep 17 00:00:00 2001 From: gautamomento Date: Mon, 1 Nov 2021 21:19:02 -0700 Subject: [PATCH 02/11] formatting --- src/momento_sdk/cache.py | 7 ++++--- src/momento_sdk/cache_name_interceptor.py | 2 +- src/momento_sdk/generic_client_interceptor.py | 2 +- src/momento_sdk/momento.py | 7 ++++--- src/momento_sdk/momento_endpoint_resolver.py | 3 ++- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/momento_sdk/cache.py b/src/momento_sdk/cache.py index 55b8e922..392f7953 100644 --- a/src/momento_sdk/cache.py +++ b/src/momento_sdk/cache.py @@ -9,10 +9,10 @@ class Cache: def __init__(self, auth_token, cache_name, endpoint, default_ttlSeconds): self._default_ttlSeconds = default_ttlSeconds self._secure_channel = grpc.secure_channel(endpoint, grpc.ssl_channel_credentials()) - auth_interceptor = authorization_interceptor.get_authorization_interceptor(auth_token=auth_token) - cache_interceptor = cache_name_interceptor.get_cache_name_interceptor(cache_name=cache_name) + auth_interceptor = authorization_interceptor.get_authorization_interceptor(auth_token) + cache_interceptor = cache_name_interceptor.get_cache_name_interceptor(cache_name) intercept_channel = grpc.intercept_channel(self._secure_channel, auth_interceptor, cache_interceptor) - self._client = cache_client.ScsStub(channel=intercept_channel) + self._client = cache_client.ScsStub(intercept_channel) # self._wait_till_ready() # Temporary hack @@ -52,4 +52,5 @@ def _asBytes(self, data, errorMessage): return data.encode('utf-8') if (isinstance(data, bytes)): return data + # TODO: Add conversions raise TypeError(errorMessage + str(type(data))) diff --git a/src/momento_sdk/cache_name_interceptor.py b/src/momento_sdk/cache_name_interceptor.py index a4d3ea5c..2b8b7780 100644 --- a/src/momento_sdk/cache_name_interceptor.py +++ b/src/momento_sdk/cache_name_interceptor.py @@ -1,4 +1,4 @@ from . import header_client_interceptor def get_cache_name_interceptor(cache_name): - return header_client_interceptor.header_adder_interceptor('cache', cache_name) \ No newline at end of file + return header_client_interceptor.header_adder_interceptor('cache', cache_name) diff --git a/src/momento_sdk/generic_client_interceptor.py b/src/momento_sdk/generic_client_interceptor.py index 849efe49..6acb4d64 100644 --- a/src/momento_sdk/generic_client_interceptor.py +++ b/src/momento_sdk/generic_client_interceptor.py @@ -13,4 +13,4 @@ def intercept_unary_unary(self, continuation, client_call_details, request): return postprocess(response) if postprocess else response def create(intercept_call): - return _GenericClientInterceptor(intercept_call) \ No newline at end of file + return _GenericClientInterceptor(intercept_call) diff --git a/src/momento_sdk/momento.py b/src/momento_sdk/momento.py index 146a17c1..b3043a37 100644 --- a/src/momento_sdk/momento.py +++ b/src/momento_sdk/momento.py @@ -7,13 +7,13 @@ class Momento: def __init__(self, auth_token, endpoint_override=None): - endpoints = momento_endpoint_resolver._resolve(auth_token=auth_token, endpoint_override=endpoint_override) + endpoints = momento_endpoint_resolver._resolve(auth_token, endpoint_override) self._auth_token = auth_token self._control_endpoint = endpoints.control_endpoint self._cache_endpoint = endpoints.cache_endpoint self._secure_channel = grpc.secure_channel(self._control_endpoint, grpc.ssl_channel_credentials()) intercept_channel = grpc.intercept_channel(self._secure_channel, authorization_interceptor.get_authorization_interceptor(auth_token)) - self._client = control_client.ScsControlStub(channel=intercept_channel) + self._client = control_client.ScsControlStub(intercept_channel) def __enter__(self): return self @@ -32,7 +32,8 @@ def delete_cache(self, cache_name): self._client.DeleteCache(request) def get_cache(self, cache_name, ttl_seconds, options=None) : - return Cache(auth_token=self._auth_token, cache_name=cache_name, endpoint=self._cache_endpoint, default_ttlSeconds=ttl_seconds) + # TODO: Do create if exists + return Cache(self._auth_token, cache_name, self._cache_endpoint, ttl_seconds) def init(auth_token): return Momento(auth_token=auth_token) diff --git a/src/momento_sdk/momento_endpoint_resolver.py b/src/momento_sdk/momento_endpoint_resolver.py index 12a99bea..4f067dc3 100644 --- a/src/momento_sdk/momento_endpoint_resolver.py +++ b/src/momento_sdk/momento_endpoint_resolver.py @@ -22,7 +22,8 @@ def _getEndpointFromToken(auth_token): try: claims = jwt.decode(auth_token, options={"verify_signature": False}) return _Endpoints(claims[_CONTROL_ENDPOINT_CLAIM_ID], claims[_CACHE_ENDPOINT_CLAIM_ID]) + # TODO: Add exception converters except DecodeError: raise Exception except KeyError: - raise Exception \ No newline at end of file + raise Exception From d12ed22fef2901cd2f675f2c7bd87d46866e7285 Mon Sep 17 00:00:00 2001 From: gautamomento Date: Mon, 1 Nov 2021 21:54:15 -0700 Subject: [PATCH 03/11] format --- src/momento_sdk/authorization_interceptor.py | 4 ++- src/momento_sdk/cache.py | 34 +++++++++++-------- src/momento_sdk/cache_name_interceptor.py | 4 ++- src/momento_sdk/generic_client_interceptor.py | 7 ++-- src/momento_sdk/header_client_interceptor.py | 5 ++- src/momento_sdk/momento.py | 30 +++++++++++----- src/momento_sdk/momento_endpoint_resolver.py | 13 ++++--- 7 files changed, 62 insertions(+), 35 deletions(-) diff --git a/src/momento_sdk/authorization_interceptor.py b/src/momento_sdk/authorization_interceptor.py index fa47107f..476e2125 100644 --- a/src/momento_sdk/authorization_interceptor.py +++ b/src/momento_sdk/authorization_interceptor.py @@ -1,4 +1,6 @@ from . import header_client_interceptor + def get_authorization_interceptor(auth_token): - return header_client_interceptor.header_adder_interceptor('authorization', auth_token) + return header_client_interceptor.header_adder_interceptor( + 'authorization', auth_token) diff --git a/src/momento_sdk/cache.py b/src/momento_sdk/cache.py index 392f7953..18741327 100644 --- a/src/momento_sdk/cache.py +++ b/src/momento_sdk/cache.py @@ -4,46 +4,52 @@ import momento_wire_types.cacheclient_pb2_grpc as cache_client import momento_wire_types.cacheclient_pb2 as cache_client_types -class Cache: +class Cache: def __init__(self, auth_token, cache_name, endpoint, default_ttlSeconds): self._default_ttlSeconds = default_ttlSeconds - self._secure_channel = grpc.secure_channel(endpoint, grpc.ssl_channel_credentials()) - auth_interceptor = authorization_interceptor.get_authorization_interceptor(auth_token) - cache_interceptor = cache_name_interceptor.get_cache_name_interceptor(cache_name) - intercept_channel = grpc.intercept_channel(self._secure_channel, auth_interceptor, cache_interceptor) + self._secure_channel = grpc.secure_channel( + endpoint, grpc.ssl_channel_credentials()) + auth_interceptor = authorization_interceptor.get_authorization_interceptor( + auth_token) + cache_interceptor = cache_name_interceptor.get_cache_name_interceptor( + cache_name) + intercept_channel = grpc.intercept_channel(self._secure_channel, + auth_interceptor, + cache_interceptor) self._client = cache_client.ScsStub(intercept_channel) # self._wait_till_ready() # Temporary hack # TODO: Make this time bound def _wait_till_ready(self): - while(True): + while (True): try: self._client.Get('b\0x01') break except: True - def __enter__(self): return self def __exit__(self, exc_type, exc_value, exc_traceback): self._secure_channel.close() - - def set(self, key , value, ttl_seconds=None): - item_ttl_seconds = self._default_ttlSeconds if ttl_seconds == None else ttl_seconds + def set(self, key, value, ttl_seconds=None): + item_ttl_seconds = self._default_ttlSeconds if ttl_seconds is None else ttl_seconds set_request = cache_client_types.SetRequest() - set_request.cache_key = self._asBytes(key, "Unsupported type for key: ") - set_request.cache_body = self._asBytes(value, "Unsupported type for value: ") + set_request.cache_key = self._asBytes(key, + "Unsupported type for key: ") + set_request.cache_body = self._asBytes(value, + "Unsupported type for value: ") set_request.ttl_milliseconds = item_ttl_seconds * 1000 self._client.Set(set_request) def get(self, key): - get_request = cache_client_types.GetRequest() - get_request.cache_key = self._asBytes(key, "Unsupported type for key: ") + get_request = cache_client_types.GetRequest() + get_request.cache_key = self._asBytes(key, + "Unsupported type for key: ") response = self._client.Get(get_request) print(response) diff --git a/src/momento_sdk/cache_name_interceptor.py b/src/momento_sdk/cache_name_interceptor.py index 2b8b7780..6752d8c7 100644 --- a/src/momento_sdk/cache_name_interceptor.py +++ b/src/momento_sdk/cache_name_interceptor.py @@ -1,4 +1,6 @@ from . import header_client_interceptor + def get_cache_name_interceptor(cache_name): - return header_client_interceptor.header_adder_interceptor('cache', cache_name) + return header_client_interceptor.header_adder_interceptor( + 'cache', cache_name) diff --git a/src/momento_sdk/generic_client_interceptor.py b/src/momento_sdk/generic_client_interceptor.py index 6acb4d64..fd5be7fe 100644 --- a/src/momento_sdk/generic_client_interceptor.py +++ b/src/momento_sdk/generic_client_interceptor.py @@ -2,15 +2,16 @@ class _GenericClientInterceptor(grpc.UnaryUnaryClientInterceptor): - def __init__(self, interceptor_function): self._fn = interceptor_function - def intercept_unary_unary(self, continuation, client_call_details, request): + def intercept_unary_unary(self, continuation, client_call_details, + request): new_details, new_request_iterator, postprocess = self._fn( - client_call_details, iter((request,)), False, False) + client_call_details, iter((request, )), False, False) response = continuation(new_details, next(new_request_iterator)) return postprocess(response) if postprocess else response + def create(intercept_call): return _GenericClientInterceptor(intercept_call) diff --git a/src/momento_sdk/header_client_interceptor.py b/src/momento_sdk/header_client_interceptor.py index 67f1fe26..96a261cf 100644 --- a/src/momento_sdk/header_client_interceptor.py +++ b/src/momento_sdk/header_client_interceptor.py @@ -13,9 +13,8 @@ class _ClientCallDetails( def header_adder_interceptor(header, value): - - def intercept_call(client_call_details, request_iterator, request_streaming, - response_streaming): + def intercept_call(client_call_details, request_iterator, + request_streaming, response_streaming): metadata = [] if client_call_details.metadata is not None: metadata = list(client_call_details.metadata) diff --git a/src/momento_sdk/momento.py b/src/momento_sdk/momento.py index b3043a37..b0eef674 100644 --- a/src/momento_sdk/momento.py +++ b/src/momento_sdk/momento.py @@ -1,18 +1,25 @@ -from momento_wire_types.controlclient_pb2 import CreateCacheRequest, DeleteCacheRequest +from momento_wire_types.controlclient_pb2 import CreateCacheRequest +from momento_wire_types.controlclient_pb2 import DeleteCacheRequest import momento_wire_types.controlclient_pb2_grpc as control_client import grpc from momento_sdk.cache import Cache from . import authorization_interceptor, momento_endpoint_resolver + class Momento: def __init__(self, auth_token, endpoint_override=None): - endpoints = momento_endpoint_resolver._resolve(auth_token, endpoint_override) + endpoints = momento_endpoint_resolver._resolve(auth_token, + endpoint_override) self._auth_token = auth_token self._control_endpoint = endpoints.control_endpoint self._cache_endpoint = endpoints.cache_endpoint - self._secure_channel = grpc.secure_channel(self._control_endpoint, grpc.ssl_channel_credentials()) - intercept_channel = grpc.intercept_channel(self._secure_channel, authorization_interceptor.get_authorization_interceptor(auth_token)) + self._secure_channel = grpc.secure_channel( + self._control_endpoint, grpc.ssl_channel_credentials()) + intercept_channel = grpc.intercept_channel( + self._secure_channel, + authorization_interceptor.get_authorization_interceptor( + auth_token)) self._client = control_client.ScsControlStub(intercept_channel) def __enter__(self): @@ -31,9 +38,16 @@ def delete_cache(self, cache_name): request.cache_name = cache_name self._client.DeleteCache(request) - def get_cache(self, cache_name, ttl_seconds, options=None) : - # TODO: Do create if exists - return Cache(self._auth_token, cache_name, self._cache_endpoint, ttl_seconds) + def get_cache(self, cache_name, ttl_seconds, create_if_absent=False): + if (create_if_absent): + try: + self.create_cache(cache_name) + except ValueError: + # Cache is already present, so continue + pass + return Cache(self._auth_token, cache_name, self._cache_endpoint, + ttl_seconds) + def init(auth_token): - return Momento(auth_token=auth_token) + return Momento(auth_token) diff --git a/src/momento_sdk/momento_endpoint_resolver.py b/src/momento_sdk/momento_endpoint_resolver.py index 4f067dc3..87eb8559 100644 --- a/src/momento_sdk/momento_endpoint_resolver.py +++ b/src/momento_sdk/momento_endpoint_resolver.py @@ -6,22 +6,25 @@ _CONTROL_ENDPOINT_CLAIM_ID = 'cp' _CACHE_ENDPOINT_CLAIM_ID = 'c' -class _Endpoints: +class _Endpoints: def __init__(self, control_endpoint, cache_endpoint): self.control_endpoint = control_endpoint self.cache_endpoint = cache_endpoint -def _resolve(auth_token, endpoint_override=None) : - if (endpoint_override != None) : - return _Endpoints(_MOMENTO_CONTROL_ENDPOINT_PREFIX + endpoint_override, _MOMENTO_CACHE_ENDPOINT_PREFIX + endpoint_override) +def _resolve(auth_token, endpoint_override=None): + if (endpoint_override is not None): + return _Endpoints(_MOMENTO_CONTROL_ENDPOINT_PREFIX + endpoint_override, + _MOMENTO_CACHE_ENDPOINT_PREFIX + endpoint_override) return _getEndpointFromToken(auth_token) + def _getEndpointFromToken(auth_token): try: claims = jwt.decode(auth_token, options={"verify_signature": False}) - return _Endpoints(claims[_CONTROL_ENDPOINT_CLAIM_ID], claims[_CACHE_ENDPOINT_CLAIM_ID]) + return _Endpoints(claims[_CONTROL_ENDPOINT_CLAIM_ID], + claims[_CACHE_ENDPOINT_CLAIM_ID]) # TODO: Add exception converters except DecodeError: raise Exception From 1b4c11a1350ee57da0f25d20fc1edb1420a0320e Mon Sep 17 00:00:00 2001 From: gautamomento Date: Tue, 2 Nov 2021 14:04:30 -0700 Subject: [PATCH 04/11] add exception handling and response wrapping --- src/momento_sdk/cache.py | 77 +++++++++++++------ src/momento_sdk/cache_operation_responses.py | 60 +++++++++++++++ .../cache_service_errors_converter.py | 40 ++++++++++ src/momento_sdk/errors.py | 67 ++++++++++++++++ src/momento_sdk/header_client_interceptor.py | 2 +- src/momento_sdk/momento.py | 34 +++++--- src/momento_sdk/momento_endpoint_resolver.py | 9 +-- 7 files changed, 246 insertions(+), 43 deletions(-) create mode 100644 src/momento_sdk/cache_operation_responses.py create mode 100644 src/momento_sdk/cache_service_errors_converter.py create mode 100644 src/momento_sdk/errors.py diff --git a/src/momento_sdk/cache.py b/src/momento_sdk/cache.py index 18741327..6577472b 100644 --- a/src/momento_sdk/cache.py +++ b/src/momento_sdk/cache.py @@ -1,12 +1,19 @@ import grpc -from . import authorization_interceptor -from . import cache_name_interceptor +import time +import uuid import momento_wire_types.cacheclient_pb2_grpc as cache_client import momento_wire_types.cacheclient_pb2 as cache_client_types +from . import cache_service_errors_converter +from . import authorization_interceptor +from . import cache_name_interceptor +from . import errors +from . import cache_operation_responses as cache_sdk_resp + class Cache: def __init__(self, auth_token, cache_name, endpoint, default_ttlSeconds): + self._validate_ttl(default_ttlSeconds) self._default_ttlSeconds = default_ttlSeconds self._secure_channel = grpc.secure_channel( endpoint, grpc.ssl_channel_credentials()) @@ -18,17 +25,24 @@ def __init__(self, auth_token, cache_name, endpoint, default_ttlSeconds): auth_interceptor, cache_interceptor) self._client = cache_client.ScsStub(intercept_channel) - # self._wait_till_ready() + self._wait_till_ready() - # Temporary hack - # TODO: Make this time bound + # Temporary measure def _wait_till_ready(self): - while (True): + start_time = time.time() + max_wait_seconds = 5 + back_off_millis = 50 + last_exception = None + + while (time.time() - start_time < max_wait_seconds): try: - self._client.Get('b\0x01') - break - except: - True + self.get(uuid.uuid1().bytes) + return + except Exception as e: + last_exception = e + time.sleep(back_off_millis / 1000.0) + + raise cache_service_errors_converter._convert(last_exception) def __enter__(self): return self @@ -37,26 +51,39 @@ def __exit__(self, exc_type, exc_value, exc_traceback): self._secure_channel.close() def set(self, key, value, ttl_seconds=None): - item_ttl_seconds = self._default_ttlSeconds if ttl_seconds is None else ttl_seconds - set_request = cache_client_types.SetRequest() - set_request.cache_key = self._asBytes(key, - "Unsupported type for key: ") - set_request.cache_body = self._asBytes(value, - "Unsupported type for value: ") - set_request.ttl_milliseconds = item_ttl_seconds * 1000 - self._client.Set(set_request) + try: + item_ttl_seconds = self._default_ttlSeconds if ttl_seconds is None else ttl_seconds + self._validate_ttl(item_ttl_seconds) + set_request = cache_client_types.SetRequest() + set_request.cache_key = self._asBytes( + key, "Unsupported type for key: ") + set_request.cache_body = self._asBytes( + value, "Unsupported type for value: ") + set_request.ttl_milliseconds = item_ttl_seconds * 1000 + response = self._client.Set(set_request) + return cache_sdk_resp.CacheSetResponse(response, + set_request.cache_body) + except Exception as e: + raise cache_service_errors_converter._convert(e) def get(self, key): - get_request = cache_client_types.GetRequest() - get_request.cache_key = self._asBytes(key, - "Unsupported type for key: ") - response = self._client.Get(get_request) - print(response) + try: + get_request = cache_client_types.GetRequest() + get_request.cache_key = self._asBytes( + key, "Unsupported type for key: ") + response = self._client.Get(get_request) + return cache_sdk_resp.CacheGetResponse(response) + except Exception as e: + raise cache_service_errors_converter._convert(e) def _asBytes(self, data, errorMessage): if (isinstance(data, str)): return data.encode('utf-8') if (isinstance(data, bytes)): return data - # TODO: Add conversions - raise TypeError(errorMessage + str(type(data))) + raise errors.InvalidInputError(errorMessage + str(type(data))) + + def _validate_ttl(self, ttl_seconds): + if (not isinstance(ttl_seconds, int) or ttl_seconds <= 0): + raise errors.InvalidInputError( + 'TTL Seconds must be a non-zero positive integer.') diff --git a/src/momento_sdk/cache_operation_responses.py b/src/momento_sdk/cache_operation_responses.py new file mode 100644 index 00000000..da537530 --- /dev/null +++ b/src/momento_sdk/cache_operation_responses.py @@ -0,0 +1,60 @@ +from enum import Enum +from momento_wire_types import cacheclient_pb2 as cache_client_types +from . import cache_service_errors_converter as error_converter + + +class CacheResult(Enum): + HIT = 1 + MISS = 2 + + +class CacheSetResponse: + def __init__(self, grpc_set_response, value): + self._value = value + if (grpc_set_response.result is not cache_client_types.Ok): + raise error_converter._convert_ecache_result( + grpc_set_response.result, grpc_set_response.message) + + def str_utf8(self): + return self._value.decode("utf-8") + + def bytes(self): + return self._value + + +class CacheGetResponse: + def __init__(self, grpc_get_response): + self._value = grpc_get_response.cache_body + + if (grpc_get_response.result is not cache_client_types.Hit + and grpc_get_response.result is not cache_client_types.Miss): + raise error_converter._convert_ecache_result( + grpc_get_response.result, grpc_get_response.message) + + if (grpc_get_response.result == cache_client_types.Hit): + self._result = CacheResult.HIT + if (grpc_get_response.result == cache_client_types.Miss): + self._result = CacheResult.MISS + + def str_utf8(self): + if (self._result == CacheResult.HIT): + return self._value.decode("utf-8") + return None + + def bytes(self): + if (self._result == CacheResult.HIT): + return self._value + return None + + def result(self): + return self._result + + +class CreateCacheResponse: + def __init__(self, grpc_create_cache_response): + pass + + +class DeleteCacheResponse: + def __init__(self, grpc_delete_cache_response): + pass diff --git a/src/momento_sdk/cache_service_errors_converter.py b/src/momento_sdk/cache_service_errors_converter.py new file mode 100644 index 00000000..917a6c77 --- /dev/null +++ b/src/momento_sdk/cache_service_errors_converter.py @@ -0,0 +1,40 @@ +import grpc +from . import errors +from momento_wire_types import cacheclient_pb2 as cache_client_types + +__rpc_to_error = { + grpc.StatusCode.ALREADY_EXISTS: errors.CacheExistsError, + grpc.StatusCode.INVALID_ARGUMENT: errors.CacheValueError, + grpc.StatusCode.NOT_FOUND: errors.CacheNotFoundError, + grpc.StatusCode.PERMISSION_DENIED: errors.PermissionError, +} + +# Till the time MR2 stops returning errors in Enums +__ecache_result_to_error = { + cache_client_types.Bad_Request: errors.CacheValueError, + cache_client_types.Internal_Server_Error: errors.InternalServerError, + cache_client_types.Service_Unavailable: errors.InternalServerError, + cache_client_types.Unauthorized: errors.PermissionError, +} + + +def _convert(exception): + if (isinstance(exception, errors.SdkError)): + return exception + + if (isinstance(exception, grpc.RpcError)): + if exception.code() in __rpc_to_error: + return __rpc_to_error[exception.code()](exception.details()) + else: + return errors.InternalServerError( + 'CacheService failed with an internal error') + + return errors.ClientSdkError('Operation failed with error: ' + + str(exception)) + + +def _convert_ecache_result(ecache_result, message): + if (ecache_result in __ecache_result_to_error): + return __ecache_result_to_error[ecache_result](message) + return errors.InternalServerError( + 'CacheService failed with an internal error') diff --git a/src/momento_sdk/errors.py b/src/momento_sdk/errors.py new file mode 100644 index 00000000..32bab896 --- /dev/null +++ b/src/momento_sdk/errors.py @@ -0,0 +1,67 @@ +class SdkError(Exception): + """Base exception for all errors raised by Sdk""" + def __init__(self, message): + super().__init__(message) + + +class ClientSdkError(SdkError): + """For all errors raised by the client. + + Indicates that the request failed on the SDK. The request either did not + make it to the service or if it did the response from the service could + not be parsed successfully. + """ + def __init__(self, message): + super().__init__(message) + + +class InvalidInputError(ClientSdkError): + """Error raised when provided input values to the SDK are invalid + + Some examples - missing required parameters, incorrect parameter + types, malformed input. + """ + def __init__(self, message): + super().__init__(message) + + +class MomentoServiceError(SdkError): + """Errors raised when Momento Service returned an error code""" + def __init__(self, message): + super().__init__(message) + + +class CacheServiceError(MomentoServiceError): + """Base class for all errors raised by the Caching Service""" + def __init__(self, message): + super().__init__(message) + + +class CacheNotFoundError(CacheServiceError): + """Error raised for operations performed on non-existent cache""" + def __init__(self, message): + super().__init__(message) + + +class CacheExistsError(CacheServiceError): + """Error raised when attempting to create a cache with same name""" + def __init__(self, message): + super().__init__(message) + + +class CacheValueError(CacheServiceError): + """Error raised when service validation fails for provided values""" + def __init__(self, message): + super().__init__(message) + + +class PermissionError(CacheServiceError): + """Error when authentication with Cache Service fails""" + def __init__(self, message): + super().__init__(message) + + +class InternalServerError(CacheServiceError): + """Operation failed on the server with an unknown error""" + def __init__(self, message): + super().__init__(message) diff --git a/src/momento_sdk/header_client_interceptor.py b/src/momento_sdk/header_client_interceptor.py index 96a261cf..fe85d5b4 100644 --- a/src/momento_sdk/header_client_interceptor.py +++ b/src/momento_sdk/header_client_interceptor.py @@ -1,7 +1,7 @@ import collections +import grpc from momento_sdk import generic_client_interceptor -import grpc class _ClientCallDetails( diff --git a/src/momento_sdk/momento.py b/src/momento_sdk/momento.py index b0eef674..4855f1c4 100644 --- a/src/momento_sdk/momento.py +++ b/src/momento_sdk/momento.py @@ -1,10 +1,14 @@ -from momento_wire_types.controlclient_pb2 import CreateCacheRequest -from momento_wire_types.controlclient_pb2 import DeleteCacheRequest -import momento_wire_types.controlclient_pb2_grpc as control_client import grpc +import momento_wire_types.controlclient_pb2_grpc as control_client -from momento_sdk.cache import Cache +from momento_wire_types.controlclient_pb2 import CreateCacheRequest +from momento_wire_types.controlclient_pb2 import DeleteCacheRequest +from . import cache_service_errors_converter +from . import errors +from .cache import Cache from . import authorization_interceptor, momento_endpoint_resolver +from .cache_operation_responses import CreateCacheResponse +from .cache_operation_responses import DeleteCacheResponse class Momento: @@ -29,21 +33,27 @@ def __exit__(self, exc_type, exc_value, exc_traceback): self._secure_channel.close() def create_cache(self, cache_name): - request = CreateCacheRequest() - request.cache_name = cache_name - self._client.CreateCache(request) + try: + request = CreateCacheRequest() + request.cache_name = cache_name + return CreateCacheResponse(self._client.CreateCache(request)) + except Exception as e: + raise cache_service_errors_converter._convert(e) from None def delete_cache(self, cache_name): - request = DeleteCacheRequest() - request.cache_name = cache_name - self._client.DeleteCache(request) + try: + request = DeleteCacheRequest() + request.cache_name = cache_name + return DeleteCacheResponse(self._client.DeleteCache(request)) + except Exception as e: + raise cache_service_errors_converter._convert(e) from None def get_cache(self, cache_name, ttl_seconds, create_if_absent=False): if (create_if_absent): try: self.create_cache(cache_name) - except ValueError: - # Cache is already present, so continue + except errors.CacheExistsError: + # Cache already exists so nothing to do pass return Cache(self._auth_token, cache_name, self._cache_endpoint, ttl_seconds) diff --git a/src/momento_sdk/momento_endpoint_resolver.py b/src/momento_sdk/momento_endpoint_resolver.py index 87eb8559..2887b025 100644 --- a/src/momento_sdk/momento_endpoint_resolver.py +++ b/src/momento_sdk/momento_endpoint_resolver.py @@ -1,6 +1,8 @@ import jwt from jwt.exceptions import DecodeError +from . import errors + _MOMENTO_CONTROL_ENDPOINT_PREFIX = 'control.' _MOMENTO_CACHE_ENDPOINT_PREFIX = 'cache.' _CONTROL_ENDPOINT_CLAIM_ID = 'cp' @@ -25,8 +27,5 @@ def _getEndpointFromToken(auth_token): claims = jwt.decode(auth_token, options={"verify_signature": False}) return _Endpoints(claims[_CONTROL_ENDPOINT_CLAIM_ID], claims[_CACHE_ENDPOINT_CLAIM_ID]) - # TODO: Add exception converters - except DecodeError: - raise Exception - except KeyError: - raise Exception + except (DecodeError, KeyError): + raise errors.InvalidInputError('Invalid Auth token.') from None From cf83e8abb51fc1be49b8c771dcd5985377636817 Mon Sep 17 00:00:00 2001 From: gautamomento Date: Tue, 2 Nov 2021 14:27:07 -0700 Subject: [PATCH 05/11] rename files --- src/momento_sdk/_authorization_interceptor.py | 6 ++++++ src/momento_sdk/_cache_name_interceptor.py | 6 ++++++ ...ter.py => _cache_service_errors_converter.py} | 4 ++-- ...rceptor.py => _generic_client_interceptor.py} | 0 ...erceptor.py => _header_client_interceptor.py} | 4 ++-- ...resolver.py => _momento_endpoint_resolver.py} | 2 +- src/momento_sdk/authorization_interceptor.py | 6 ------ src/momento_sdk/cache.py | 16 ++++++++-------- src/momento_sdk/cache_name_interceptor.py | 6 ------ src/momento_sdk/cache_operation_responses.py | 6 +++--- src/momento_sdk/momento.py | 13 +++++++------ 11 files changed, 35 insertions(+), 34 deletions(-) create mode 100644 src/momento_sdk/_authorization_interceptor.py create mode 100644 src/momento_sdk/_cache_name_interceptor.py rename src/momento_sdk/{cache_service_errors_converter.py => _cache_service_errors_converter.py} (94%) rename src/momento_sdk/{generic_client_interceptor.py => _generic_client_interceptor.py} (100%) rename src/momento_sdk/{header_client_interceptor.py => _header_client_interceptor.py} (88%) rename src/momento_sdk/{momento_endpoint_resolver.py => _momento_endpoint_resolver.py} (95%) delete mode 100644 src/momento_sdk/authorization_interceptor.py delete mode 100644 src/momento_sdk/cache_name_interceptor.py diff --git a/src/momento_sdk/_authorization_interceptor.py b/src/momento_sdk/_authorization_interceptor.py new file mode 100644 index 00000000..c32f7df8 --- /dev/null +++ b/src/momento_sdk/_authorization_interceptor.py @@ -0,0 +1,6 @@ +from . import _header_client_interceptor + + +def get_authorization_interceptor(auth_token): + return _header_client_interceptor.header_adder_interceptor( + 'authorization', auth_token) diff --git a/src/momento_sdk/_cache_name_interceptor.py b/src/momento_sdk/_cache_name_interceptor.py new file mode 100644 index 00000000..1979f922 --- /dev/null +++ b/src/momento_sdk/_cache_name_interceptor.py @@ -0,0 +1,6 @@ +from . import _header_client_interceptor + + +def get_cache_name_interceptor(cache_name): + return _header_client_interceptor.header_adder_interceptor( + 'cache', cache_name) diff --git a/src/momento_sdk/cache_service_errors_converter.py b/src/momento_sdk/_cache_service_errors_converter.py similarity index 94% rename from src/momento_sdk/cache_service_errors_converter.py rename to src/momento_sdk/_cache_service_errors_converter.py index 917a6c77..0b0d80a4 100644 --- a/src/momento_sdk/cache_service_errors_converter.py +++ b/src/momento_sdk/_cache_service_errors_converter.py @@ -18,7 +18,7 @@ } -def _convert(exception): +def convert(exception): if (isinstance(exception, errors.SdkError)): return exception @@ -33,7 +33,7 @@ def _convert(exception): str(exception)) -def _convert_ecache_result(ecache_result, message): +def convert_ecache_result(ecache_result, message): if (ecache_result in __ecache_result_to_error): return __ecache_result_to_error[ecache_result](message) return errors.InternalServerError( diff --git a/src/momento_sdk/generic_client_interceptor.py b/src/momento_sdk/_generic_client_interceptor.py similarity index 100% rename from src/momento_sdk/generic_client_interceptor.py rename to src/momento_sdk/_generic_client_interceptor.py diff --git a/src/momento_sdk/header_client_interceptor.py b/src/momento_sdk/_header_client_interceptor.py similarity index 88% rename from src/momento_sdk/header_client_interceptor.py rename to src/momento_sdk/_header_client_interceptor.py index fe85d5b4..456a2ba8 100644 --- a/src/momento_sdk/header_client_interceptor.py +++ b/src/momento_sdk/_header_client_interceptor.py @@ -1,7 +1,7 @@ import collections import grpc -from momento_sdk import generic_client_interceptor +from momento_sdk import _generic_client_interceptor class _ClientCallDetails( @@ -27,4 +27,4 @@ def intercept_call(client_call_details, request_iterator, client_call_details.credentials) return client_call_details, request_iterator, None - return generic_client_interceptor.create(intercept_call) + return _generic_client_interceptor.create(intercept_call) diff --git a/src/momento_sdk/momento_endpoint_resolver.py b/src/momento_sdk/_momento_endpoint_resolver.py similarity index 95% rename from src/momento_sdk/momento_endpoint_resolver.py rename to src/momento_sdk/_momento_endpoint_resolver.py index 2887b025..de99e67a 100644 --- a/src/momento_sdk/momento_endpoint_resolver.py +++ b/src/momento_sdk/_momento_endpoint_resolver.py @@ -15,7 +15,7 @@ def __init__(self, control_endpoint, cache_endpoint): self.cache_endpoint = cache_endpoint -def _resolve(auth_token, endpoint_override=None): +def resolve(auth_token, endpoint_override=None): if (endpoint_override is not None): return _Endpoints(_MOMENTO_CONTROL_ENDPOINT_PREFIX + endpoint_override, _MOMENTO_CACHE_ENDPOINT_PREFIX + endpoint_override) diff --git a/src/momento_sdk/authorization_interceptor.py b/src/momento_sdk/authorization_interceptor.py deleted file mode 100644 index 476e2125..00000000 --- a/src/momento_sdk/authorization_interceptor.py +++ /dev/null @@ -1,6 +0,0 @@ -from . import header_client_interceptor - - -def get_authorization_interceptor(auth_token): - return header_client_interceptor.header_adder_interceptor( - 'authorization', auth_token) diff --git a/src/momento_sdk/cache.py b/src/momento_sdk/cache.py index 6577472b..2b467778 100644 --- a/src/momento_sdk/cache.py +++ b/src/momento_sdk/cache.py @@ -4,9 +4,9 @@ import momento_wire_types.cacheclient_pb2_grpc as cache_client import momento_wire_types.cacheclient_pb2 as cache_client_types -from . import cache_service_errors_converter -from . import authorization_interceptor -from . import cache_name_interceptor +from . import _cache_service_errors_converter +from . import _authorization_interceptor +from . import _cache_name_interceptor from . import errors from . import cache_operation_responses as cache_sdk_resp @@ -17,9 +17,9 @@ def __init__(self, auth_token, cache_name, endpoint, default_ttlSeconds): self._default_ttlSeconds = default_ttlSeconds self._secure_channel = grpc.secure_channel( endpoint, grpc.ssl_channel_credentials()) - auth_interceptor = authorization_interceptor.get_authorization_interceptor( + auth_interceptor = _authorization_interceptor.get_authorization_interceptor( auth_token) - cache_interceptor = cache_name_interceptor.get_cache_name_interceptor( + cache_interceptor = _cache_name_interceptor.get_cache_name_interceptor( cache_name) intercept_channel = grpc.intercept_channel(self._secure_channel, auth_interceptor, @@ -42,7 +42,7 @@ def _wait_till_ready(self): last_exception = e time.sleep(back_off_millis / 1000.0) - raise cache_service_errors_converter._convert(last_exception) + raise _cache_service_errors_converter.convert(last_exception) def __enter__(self): return self @@ -64,7 +64,7 @@ def set(self, key, value, ttl_seconds=None): return cache_sdk_resp.CacheSetResponse(response, set_request.cache_body) except Exception as e: - raise cache_service_errors_converter._convert(e) + raise _cache_service_errors_converter.convert(e) def get(self, key): try: @@ -74,7 +74,7 @@ def get(self, key): response = self._client.Get(get_request) return cache_sdk_resp.CacheGetResponse(response) except Exception as e: - raise cache_service_errors_converter._convert(e) + raise _cache_service_errors_converter.convert(e) def _asBytes(self, data, errorMessage): if (isinstance(data, str)): diff --git a/src/momento_sdk/cache_name_interceptor.py b/src/momento_sdk/cache_name_interceptor.py deleted file mode 100644 index 6752d8c7..00000000 --- a/src/momento_sdk/cache_name_interceptor.py +++ /dev/null @@ -1,6 +0,0 @@ -from . import header_client_interceptor - - -def get_cache_name_interceptor(cache_name): - return header_client_interceptor.header_adder_interceptor( - 'cache', cache_name) diff --git a/src/momento_sdk/cache_operation_responses.py b/src/momento_sdk/cache_operation_responses.py index da537530..425e107e 100644 --- a/src/momento_sdk/cache_operation_responses.py +++ b/src/momento_sdk/cache_operation_responses.py @@ -1,6 +1,6 @@ from enum import Enum from momento_wire_types import cacheclient_pb2 as cache_client_types -from . import cache_service_errors_converter as error_converter +from . import _cache_service_errors_converter as error_converter class CacheResult(Enum): @@ -12,7 +12,7 @@ class CacheSetResponse: def __init__(self, grpc_set_response, value): self._value = value if (grpc_set_response.result is not cache_client_types.Ok): - raise error_converter._convert_ecache_result( + raise error_converter.convert_ecache_result( grpc_set_response.result, grpc_set_response.message) def str_utf8(self): @@ -28,7 +28,7 @@ def __init__(self, grpc_get_response): if (grpc_get_response.result is not cache_client_types.Hit and grpc_get_response.result is not cache_client_types.Miss): - raise error_converter._convert_ecache_result( + raise error_converter.convert_ecache_result( grpc_get_response.result, grpc_get_response.message) if (grpc_get_response.result == cache_client_types.Hit): diff --git a/src/momento_sdk/momento.py b/src/momento_sdk/momento.py index 4855f1c4..c7da12db 100644 --- a/src/momento_sdk/momento.py +++ b/src/momento_sdk/momento.py @@ -3,17 +3,18 @@ from momento_wire_types.controlclient_pb2 import CreateCacheRequest from momento_wire_types.controlclient_pb2 import DeleteCacheRequest -from . import cache_service_errors_converter +from . import _cache_service_errors_converter from . import errors from .cache import Cache -from . import authorization_interceptor, momento_endpoint_resolver +from . import _authorization_interceptor +from . import _momento_endpoint_resolver from .cache_operation_responses import CreateCacheResponse from .cache_operation_responses import DeleteCacheResponse class Momento: def __init__(self, auth_token, endpoint_override=None): - endpoints = momento_endpoint_resolver._resolve(auth_token, + endpoints = _momento_endpoint_resolver.resolve(auth_token, endpoint_override) self._auth_token = auth_token self._control_endpoint = endpoints.control_endpoint @@ -22,7 +23,7 @@ def __init__(self, auth_token, endpoint_override=None): self._control_endpoint, grpc.ssl_channel_credentials()) intercept_channel = grpc.intercept_channel( self._secure_channel, - authorization_interceptor.get_authorization_interceptor( + _authorization_interceptor.get_authorization_interceptor( auth_token)) self._client = control_client.ScsControlStub(intercept_channel) @@ -38,7 +39,7 @@ def create_cache(self, cache_name): request.cache_name = cache_name return CreateCacheResponse(self._client.CreateCache(request)) except Exception as e: - raise cache_service_errors_converter._convert(e) from None + raise _cache_service_errors_converter.convert(e) from None def delete_cache(self, cache_name): try: @@ -46,7 +47,7 @@ def delete_cache(self, cache_name): request.cache_name = cache_name return DeleteCacheResponse(self._client.DeleteCache(request)) except Exception as e: - raise cache_service_errors_converter._convert(e) from None + raise _cache_service_errors_converter.convert(e) from None def get_cache(self, cache_name, ttl_seconds, create_if_absent=False): if (create_if_absent): From b6fa5de5db95d5377426dfbd4404d4c25ee672b2 Mon Sep 17 00:00:00 2001 From: gautamomento Date: Tue, 2 Nov 2021 15:49:56 -0700 Subject: [PATCH 06/11] formatting --- src/momento_sdk/_momento_endpoint_resolver.py | 2 +- src/momento_sdk/cache.py | 6 +++--- src/momento_sdk/cache_operation_responses.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/momento_sdk/_momento_endpoint_resolver.py b/src/momento_sdk/_momento_endpoint_resolver.py index de99e67a..81cb4e73 100644 --- a/src/momento_sdk/_momento_endpoint_resolver.py +++ b/src/momento_sdk/_momento_endpoint_resolver.py @@ -24,7 +24,7 @@ def resolve(auth_token, endpoint_override=None): def _getEndpointFromToken(auth_token): try: - claims = jwt.decode(auth_token, options={"verify_signature": False}) + claims = jwt.decode(auth_token, options={'verify_signature': False}) return _Endpoints(claims[_CONTROL_ENDPOINT_CLAIM_ID], claims[_CACHE_ENDPOINT_CLAIM_ID]) except (DecodeError, KeyError): diff --git a/src/momento_sdk/cache.py b/src/momento_sdk/cache.py index 2b467778..685d4220 100644 --- a/src/momento_sdk/cache.py +++ b/src/momento_sdk/cache.py @@ -56,9 +56,9 @@ def set(self, key, value, ttl_seconds=None): self._validate_ttl(item_ttl_seconds) set_request = cache_client_types.SetRequest() set_request.cache_key = self._asBytes( - key, "Unsupported type for key: ") + key, 'Unsupported type for key: ') set_request.cache_body = self._asBytes( - value, "Unsupported type for value: ") + value, 'Unsupported type for value: ') set_request.ttl_milliseconds = item_ttl_seconds * 1000 response = self._client.Set(set_request) return cache_sdk_resp.CacheSetResponse(response, @@ -70,7 +70,7 @@ def get(self, key): try: get_request = cache_client_types.GetRequest() get_request.cache_key = self._asBytes( - key, "Unsupported type for key: ") + key, 'Unsupported type for key: ') response = self._client.Get(get_request) return cache_sdk_resp.CacheGetResponse(response) except Exception as e: diff --git a/src/momento_sdk/cache_operation_responses.py b/src/momento_sdk/cache_operation_responses.py index 425e107e..35794eb1 100644 --- a/src/momento_sdk/cache_operation_responses.py +++ b/src/momento_sdk/cache_operation_responses.py @@ -16,7 +16,7 @@ def __init__(self, grpc_set_response, value): grpc_set_response.result, grpc_set_response.message) def str_utf8(self): - return self._value.decode("utf-8") + return self._value.decode('utf-8') def bytes(self): return self._value @@ -38,7 +38,7 @@ def __init__(self, grpc_get_response): def str_utf8(self): if (self._result == CacheResult.HIT): - return self._value.decode("utf-8") + return self._value.decode('utf-8') return None def bytes(self): From de3c8f3b2fde27789f8d40f92bb7b0408525fafa Mon Sep 17 00:00:00 2001 From: gautamomento Date: Wed, 3 Nov 2021 10:28:04 -0700 Subject: [PATCH 07/11] rename packaging --- .gitignore | 4 ++-- setup.cfg | 2 +- src/{momento_sdk => momento}/__init__.py | 0 src/{momento_sdk => momento}/_authorization_interceptor.py | 0 src/{momento_sdk => momento}/_cache_name_interceptor.py | 0 .../_cache_service_errors_converter.py | 0 src/{momento_sdk => momento}/_generic_client_interceptor.py | 0 src/{momento_sdk => momento}/_header_client_interceptor.py | 2 +- src/{momento_sdk => momento}/_momento_endpoint_resolver.py | 0 src/{momento_sdk => momento}/cache.py | 0 src/{momento_sdk => momento}/cache_operation_responses.py | 0 src/{momento_sdk => momento}/errors.py | 0 src/{momento_sdk => momento}/momento.py | 0 13 files changed, 4 insertions(+), 4 deletions(-) rename src/{momento_sdk => momento}/__init__.py (100%) rename src/{momento_sdk => momento}/_authorization_interceptor.py (100%) rename src/{momento_sdk => momento}/_cache_name_interceptor.py (100%) rename src/{momento_sdk => momento}/_cache_service_errors_converter.py (100%) rename src/{momento_sdk => momento}/_generic_client_interceptor.py (100%) rename src/{momento_sdk => momento}/_header_client_interceptor.py (94%) rename src/{momento_sdk => momento}/_momento_endpoint_resolver.py (100%) rename src/{momento_sdk => momento}/cache.py (100%) rename src/{momento_sdk => momento}/cache_operation_responses.py (100%) rename src/{momento_sdk => momento}/errors.py (100%) rename src/{momento_sdk => momento}/momento.py (100%) diff --git a/.gitignore b/.gitignore index a2773a6e..3829d555 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # Compiled python modules. src/*.pyc -src/momento_sdk/*.pyc -src/momento_sdk/__pycache__ +src/momento/*.pyc +src/momento/__pycache__ # Setuptools distribution folder. /dist/ diff --git a/setup.cfg b/setup.cfg index 259d432e..e8220b5b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [metadata] -name = momento-sdk +name = momento [options] package_dir = diff --git a/src/momento_sdk/__init__.py b/src/momento/__init__.py similarity index 100% rename from src/momento_sdk/__init__.py rename to src/momento/__init__.py diff --git a/src/momento_sdk/_authorization_interceptor.py b/src/momento/_authorization_interceptor.py similarity index 100% rename from src/momento_sdk/_authorization_interceptor.py rename to src/momento/_authorization_interceptor.py diff --git a/src/momento_sdk/_cache_name_interceptor.py b/src/momento/_cache_name_interceptor.py similarity index 100% rename from src/momento_sdk/_cache_name_interceptor.py rename to src/momento/_cache_name_interceptor.py diff --git a/src/momento_sdk/_cache_service_errors_converter.py b/src/momento/_cache_service_errors_converter.py similarity index 100% rename from src/momento_sdk/_cache_service_errors_converter.py rename to src/momento/_cache_service_errors_converter.py diff --git a/src/momento_sdk/_generic_client_interceptor.py b/src/momento/_generic_client_interceptor.py similarity index 100% rename from src/momento_sdk/_generic_client_interceptor.py rename to src/momento/_generic_client_interceptor.py diff --git a/src/momento_sdk/_header_client_interceptor.py b/src/momento/_header_client_interceptor.py similarity index 94% rename from src/momento_sdk/_header_client_interceptor.py rename to src/momento/_header_client_interceptor.py index 456a2ba8..6c776367 100644 --- a/src/momento_sdk/_header_client_interceptor.py +++ b/src/momento/_header_client_interceptor.py @@ -1,7 +1,7 @@ import collections import grpc -from momento_sdk import _generic_client_interceptor +from . import _generic_client_interceptor class _ClientCallDetails( diff --git a/src/momento_sdk/_momento_endpoint_resolver.py b/src/momento/_momento_endpoint_resolver.py similarity index 100% rename from src/momento_sdk/_momento_endpoint_resolver.py rename to src/momento/_momento_endpoint_resolver.py diff --git a/src/momento_sdk/cache.py b/src/momento/cache.py similarity index 100% rename from src/momento_sdk/cache.py rename to src/momento/cache.py diff --git a/src/momento_sdk/cache_operation_responses.py b/src/momento/cache_operation_responses.py similarity index 100% rename from src/momento_sdk/cache_operation_responses.py rename to src/momento/cache_operation_responses.py diff --git a/src/momento_sdk/errors.py b/src/momento/errors.py similarity index 100% rename from src/momento_sdk/errors.py rename to src/momento/errors.py diff --git a/src/momento_sdk/momento.py b/src/momento/momento.py similarity index 100% rename from src/momento_sdk/momento.py rename to src/momento/momento.py From b54340f69a85a0d544ecca919047d63a529cba26 Mon Sep 17 00:00:00 2001 From: gautamomento Date: Thu, 4 Nov 2021 08:54:08 -0700 Subject: [PATCH 08/11] add artifactory publish logic --- .github/workflows/push_request.yml | 72 ++++++++++++++++++++++++++++++ artifactory_setup/jfrog_setup.sh | 11 +++++ setup.py | 2 +- 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/push_request.yml create mode 100755 artifactory_setup/jfrog_setup.sh diff --git a/.github/workflows/push_request.yml b/.github/workflows/push_request.yml new file mode 100644 index 00000000..66317f6a --- /dev/null +++ b/.github/workflows/push_request.yml @@ -0,0 +1,72 @@ +name: On Push + +# If this is made manual, then also update the pull_request.yml, to run builds +# on main on merge. +on: + push: + branches: [ main ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + release: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.release.outputs.release }} + steps: + - uses: actions/checkout@v2 + + - name: Set release + id: semrel + uses: go-semantic-release/action@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + allow-initial-development-versions: true + force-bump-patch-version: true + + - name: Output release + id: release + run: echo "::set-output name=release::${{ steps.semrel.outputs.version }}" + + publish_python: + # The type of runner that the job will run on + runs-on: ubuntu-latest + needs: release + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + - uses: actions/checkout@v2 + + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + + - name: Install python dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine build + - name: Configure Artifactory publish credentials + run: | + set -e + set -x + pushd artifactory_setup + ./jfrog_setup.sh deploy-user ${{ secrets.PUBLIC_ARTIFACTORY_DEPLOY_USER_MAGIC_PASSTOKEN }} >> ~/.pypirc + popd + shell: bash + + - name: Build and publish package + run: | + set -e + set -x + export MOMENTO_SDK_VERSION="${{needs.release.outputs.version}}" + if [ -z "$MOMENTO_SDK_VERSION"] + then + echo "Using default version" + export MOMENTO_SDK_VERSION="0.0.dev"`date +%s` + fi + echo "MOMENTO_SDK_VERSION=${MOMENTO_SDK_VERSION}" + python -m build + twine upload -r local dist/* --config-file ~/.pypirc + shell: bash \ No newline at end of file diff --git a/artifactory_setup/jfrog_setup.sh b/artifactory_setup/jfrog_setup.sh new file mode 100755 index 00000000..e7b8a499 --- /dev/null +++ b/artifactory_setup/jfrog_setup.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# Configure Git Hub Workflow environment for Uploading to JFrog +set -e +set -x +echo "[distutils]" +echo "index-servers = local" +echo "[local]" +echo "repository: https://momento.jfrog.io/artifactory/api/pypi/pypi-local" +echo "username: $1" +echo "password: $2" +echo "" diff --git a/setup.py b/setup.py index 2a836a9c..6a8bdb14 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ import os import time -version = os.getenv("PYPI_MOMENTO_WIRE_TYPE_VERSION") +version = os.getenv("MOMENTO_SDK_VERSION") if [version == None]: version = '0.0.dev' From a998221115b65898b03039f350a4bb64e72b1b86 Mon Sep 17 00:00:00 2001 From: gautamomento Date: Thu, 4 Nov 2021 08:55:24 -0700 Subject: [PATCH 09/11] Change method name --- src/momento/cache.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/momento/cache.py b/src/momento/cache.py index 685d4220..36451af7 100644 --- a/src/momento/cache.py +++ b/src/momento/cache.py @@ -25,10 +25,10 @@ def __init__(self, auth_token, cache_name, endpoint, default_ttlSeconds): auth_interceptor, cache_interceptor) self._client = cache_client.ScsStub(intercept_channel) - self._wait_till_ready() + self._wait_until_ready() # Temporary measure - def _wait_till_ready(self): + def _wait_until_ready(self): start_time = time.time() max_wait_seconds = 5 back_off_millis = 50 From d532d7688f533752c314b5b1b960ba5a9c4f7d08 Mon Sep 17 00:00:00 2001 From: gautamomento Date: Thu, 4 Nov 2021 08:57:43 -0700 Subject: [PATCH 10/11] add newline at the bottom --- .github/workflows/push_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push_request.yml b/.github/workflows/push_request.yml index 66317f6a..b4de41c6 100644 --- a/.github/workflows/push_request.yml +++ b/.github/workflows/push_request.yml @@ -69,4 +69,4 @@ jobs: echo "MOMENTO_SDK_VERSION=${MOMENTO_SDK_VERSION}" python -m build twine upload -r local dist/* --config-file ~/.pypirc - shell: bash \ No newline at end of file + shell: bash From 4b5813d319274706f49b488a597cacd7042e7d8b Mon Sep 17 00:00:00 2001 From: gautamomento Date: Thu, 4 Nov 2021 09:24:37 -0700 Subject: [PATCH 11/11] Update README --- README.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1e3b9545..eb059c11 100644 --- a/README.md +++ b/README.md @@ -12,4 +12,20 @@ The script creates a python virtual environment and installs dependencies ## Setting up IDE ### Visual Studio Code Use `Cmd` + `Shift` + `P` to search for `Python: Interpreter` and select: -`./client_sdk_python_env/bin/python` \ No newline at end of file +`./client_sdk_python_env/bin/python` + +# Developing +Once your pre-requisites are accomplished + +Run the following command to start your virtual environment + +`source client_sdk_python_env/bin/activate` + +To install the package under development + +`pip install -e .` + +To test your changes you can then just run your python shell as follows: + +`python` this will start the interactive shell or if you prefer you may put all +your code in a my_test.py file and run `python my_test.py`