Skip to content

Commit 3f687ec

Browse files
committed
Add caching for Management API
1 parent d8ef7fd commit 3f687ec

File tree

2 files changed

+73
-30
lines changed

2 files changed

+73
-30
lines changed

singlestoredb/management/utils.py

Lines changed: 67 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
#!/usr/bin/env python
22
"""SingleStoreDB Cluster Management."""
33
import datetime
4+
import functools
45
import os
56
import re
67
import sys
78
from typing import Any
9+
from typing import Callable
810
from typing import Dict
911
from typing import List
1012
from typing import Optional
@@ -31,37 +33,44 @@
3133
PathLikeABC = os.PathLike[str]
3234

3335

34-
def get_token() -> Optional[str]:
35-
"""Return the token for the Management API."""
36-
# See if an API key is configured
37-
tok = get_option('management.token')
38-
if tok:
39-
return tok
36+
class TTLProperty(object):
37+
"""Property with time limit."""
4038

41-
# See if the connection URL contains a JWT
42-
url = get_option('host')
43-
if not url:
44-
return None
39+
def __init__(self, fget: Callable[[Any], Any], ttl: datetime.timedelta):
40+
self.fget = fget
41+
self.ttl = ttl
42+
self._last_executed = datetime.datetime(2000, 1, 1)
43+
self._last_result = None
44+
self.__doc__ = fget.__doc__
45+
self._name = ''
4546

46-
urlp = urlparse(url, scheme='singlestoredb', allow_fragments=True)
47-
if urlp.password:
48-
try:
49-
jwt.decode(urlp.password, options={'verify_signature': False})
50-
return urlp.password
51-
except jwt.DecodeError:
52-
pass
47+
def reset(self) -> None:
48+
self._last_executed = datetime.datetime(2000, 1, 1)
49+
self._last_result = None
5350

54-
# Didn't find a key anywhere
55-
return None
51+
def __set_name__(self, owner: Any, name: str) -> None:
52+
self._name = name
5653

54+
def __get__(self, obj: Any, objtype: Any = None) -> Any:
55+
if obj is None:
56+
return self
5757

58-
def get_organization() -> Optional[str]:
59-
"""Return the organization for the current token or environment."""
60-
org = os.environ.get('SINGLESTOREDB_ORGANIZATION')
61-
if org:
62-
return org
58+
if self._last_result is not None \
59+
and (datetime.datetime.now() - self._last_executed) < self.ttl:
60+
return self._last_result
6361

64-
return None
62+
self._last_result = self.fget(obj)
63+
self._last_executed = datetime.datetime.now()
64+
65+
return self._last_result
66+
67+
68+
def ttl_property(ttl: datetime.timedelta) -> Callable[[Any], Any]:
69+
"""Property with a time-to-live."""
70+
def wrapper(func: Callable[[Any], Any]) -> Any:
71+
out = TTLProperty(func, ttl=ttl)
72+
return functools.wraps(func)(out)
73+
return wrapper
6574

6675

6776
class NamedList(List[T]):
@@ -107,6 +116,39 @@ def get(self, name_or_id: str, *default: Any) -> Any:
107116
raise
108117

109118

119+
def get_token() -> Optional[str]:
120+
"""Return the token for the Management API."""
121+
# See if an API key is configured
122+
tok = get_option('management.token')
123+
if tok:
124+
return tok
125+
126+
# See if the connection URL contains a JWT
127+
url = get_option('host')
128+
if not url:
129+
return None
130+
131+
urlp = urlparse(url, scheme='singlestoredb', allow_fragments=True)
132+
if urlp.password:
133+
try:
134+
jwt.decode(urlp.password, options={'verify_signature': False})
135+
return urlp.password
136+
except jwt.DecodeError:
137+
pass
138+
139+
# Didn't find a key anywhere
140+
return None
141+
142+
143+
def get_organization() -> Optional[str]:
144+
"""Return the organization for the current token or environment."""
145+
org = os.environ.get('SINGLESTOREDB_ORGANIZATION')
146+
if org:
147+
return org
148+
149+
return None
150+
151+
110152
def enable_http_tracing() -> None:
111153
"""Enable tracing of HTTP requests."""
112154
import logging

singlestoredb/management/workspace.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from .utils import PathLike
2929
from .utils import snake_to_camel
3030
from .utils import to_datetime
31+
from .utils import ttl_property
3132
from .utils import vars_to_str
3233

3334

@@ -1119,7 +1120,7 @@ def from_dict(
11191120
try:
11201121
region = [x for x in manager.regions if x.id == obj['regionID']][0]
11211122
except IndexError:
1122-
region = None
1123+
region = Region(obj.get('regionID', '<unknown>'), '<unknown>', '<unknown>')
11231124
out = cls(
11241125
name=obj['name'],
11251126
id=obj['workspaceGroupID'],
@@ -1377,23 +1378,23 @@ class WorkspaceManager(Manager):
13771378
#: Object type
13781379
obj_type = 'workspace'
13791380

1380-
@ property
1381+
@property
13811382
def workspace_groups(self) -> NamedList[WorkspaceGroup]:
13821383
"""Return a list of available workspace groups."""
13831384
res = self._get('workspaceGroups')
13841385
return NamedList([WorkspaceGroup.from_dict(item, self) for item in res.json()])
13851386

1386-
@ property
1387+
@property
13871388
def organizations(self) -> Organizations:
13881389
"""Return the organizations."""
13891390
return Organizations(self)
13901391

1391-
@ property
1392+
@property
13921393
def billing(self) -> Billing:
13931394
"""Return the current billing information."""
13941395
return Billing(self)
13951396

1396-
@ property
1397+
@ttl_property(datetime.timedelta(hours=1))
13971398
def regions(self) -> NamedList[Region]:
13981399
"""Return a list of available regions."""
13991400
res = self._get('regions')

0 commit comments

Comments
 (0)