Skip to content

Commit 5e7b557

Browse files
authored
Merge pull request #146 from aperture-data/release-0.3.2
Release 0.3.2
2 parents 451eb6a + 1cc0a58 commit 5e7b557

File tree

7 files changed

+117
-54
lines changed

7 files changed

+117
-54
lines changed

aperturedb/Connector.py

Lines changed: 83 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@
2424
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2525
# THE SOFTWARE.
2626
#
27-
27+
from __future__ import annotations
2828
from . import queryMessage_pb2
2929
import sys
30+
import traceback
3031
import os
3132
import socket
3233
import struct
@@ -35,6 +36,8 @@
3536
import ssl
3637
import logging
3738

39+
from threading import Lock
40+
from types import SimpleNamespace
3841
from dataclasses import dataclass
3942

4043
logger = logging.getLogger(__name__)
@@ -43,6 +46,10 @@
4346
PROTOCOL_VERSION = 1
4447

4548

49+
class UnauthorizedException(Exception):
50+
pass
51+
52+
4653
@dataclass
4754
class Session():
4855

@@ -55,7 +62,8 @@ class Session():
5562
def valid(self) -> bool:
5663
session_age = time.time() - self.session_started
5764

58-
if session_age > self.session_token_ttl:
65+
# This triggers refresh if the session is about to expire.
66+
if session_age > self.session_token_ttl - os.getenv("SESSION_EXPIRTY_OFFSET_SEC", 10):
5967
return False
6068

6169
return True
@@ -81,8 +89,7 @@ class Connector(object):
8189

8290
def __init__(self, host="localhost", port=55555,
8391
user="", password="", token="",
84-
session=None,
85-
use_ssl=True):
92+
use_ssl=True, shared_data=None):
8693

8794
self.use_ssl = use_ssl
8895

@@ -93,17 +100,18 @@ def __init__(self, host="localhost", port=55555,
93100
self.last_response = ''
94101
self.last_query_time = 0
95102

96-
self.session = None
97-
98103
self._connect()
99104

100-
if session:
101-
self.session = session
102-
else:
105+
if shared_data is None:
106+
self.shared_data = SimpleNamespace()
107+
self.shared_data.session = None
108+
self.shared_data.lock = Lock()
103109
try:
104110
self._authenticate(user, password, token)
105111
except Exception as e:
106112
raise Exception("Authentication failed:", str(e))
113+
else:
114+
self.shared_data = shared_data
107115

108116
def __del__(self):
109117

@@ -152,39 +160,44 @@ def _authenticate(self, user, password="", token=""):
152160
if session_info["status"] != 0:
153161
raise Exception(session_info["info"])
154162

155-
self.session = Session(session_info["session_token"],
156-
session_info["refresh_token"],
157-
session_info["session_token_expires_in"],
158-
session_info["refresh_token_expires_in"],
159-
)
163+
self.shared_data.session = Session(session_info["session_token"],
164+
session_info["refresh_token"],
165+
session_info["session_token_expires_in"],
166+
session_info["refresh_token_expires_in"],
167+
time.time()
168+
)
160169

161170
def _check_session_status(self):
162-
163-
if not self.session:
171+
if not self.shared_data.session:
164172
return
165173

166-
if not self.session.valid():
167-
self._refresh_token()
174+
if not self.shared_data.session.valid():
175+
with self.shared_data.lock:
176+
self._refresh_token()
168177

169178
def _refresh_token(self):
170-
171179
query = [{
172180
"RefreshToken": {
173-
"refresh_token": self.session.refresh_token
181+
"refresh_token": self.shared_data.session.refresh_token
174182
}
175183
}]
176184

177185
response, _ = self._query(query, [])
178186

179-
session_info = response[0]["RefreshToken"]
180-
if session_info["status"] != 0:
181-
raise Exception(session_info["info"])
182-
183-
self.session = Session(session_info["session_token"],
184-
session_info["refresh_token"],
185-
session_info["session_token_expires_in"],
186-
session_info["refresh_token_expires_in"],
187-
)
187+
logger.info(f"Refresh token response: \r\n{response}")
188+
if isinstance(response, list):
189+
session_info = response[0]["RefreshToken"]
190+
if session_info["status"] != 0:
191+
raise UnauthorizedException(response)
192+
193+
self.shared_data.session = Session(session_info["session_token"],
194+
session_info["refresh_token"],
195+
session_info["session_token_expires_in"],
196+
session_info["refresh_token_expires_in"],
197+
time.time()
198+
)
199+
else:
200+
raise UnauthorizedException(response)
188201

189202
def _connect(self):
190203

@@ -258,8 +271,8 @@ def _query(self, query, blob_array = []):
258271
query_msg.json = query_str
259272

260273
# Set Auth token, only when not authenticated before
261-
if self.session:
262-
query_msg.token = self.session.session_token
274+
if self.shared_data.session:
275+
query_msg.token = self.shared_data.session.session_token
263276

264277
for blob in blob_array:
265278
query_msg.blobs.append(blob)
@@ -280,21 +293,53 @@ def _query(self, query, blob_array = []):
280293
return (self.last_response, response_blob_array)
281294

282295
def query(self, q, blobs=[]):
283-
284-
self._check_session_status()
285-
296+
"""
297+
Query the database with a query string or a json object.
298+
First it checks if the session is valid, if not, it refreshes the token.
299+
Then it sends the query to the server and returns the response.
300+
301+
Args:
302+
q (json): native query to be sent
303+
blobs (list, optional): Blobs if needed with the query. Defaults to [].
304+
305+
Raises:
306+
ConnectionError: Fatal error, connection to server lost
307+
308+
Returns:
309+
_type_: _description_
310+
"""
311+
self._renew_session()
286312
try:
287313
start = time.time()
288314
self.response, self.blobs = self._query(q, blobs)
315+
if not isinstance(self.response, list) and self.response["info"] == "Not Authenticated!":
316+
# The case where session is valid, but expires while query is sent.
317+
# Hope is that the query send won't be longer than the session ttl.
318+
logger.warn(
319+
f"Session expired while query was sent. Retrying... \r\n{traceback.format_stack(limit=5)}")
320+
self._renew_session()
321+
start = time.time()
322+
self.response, self.blobs = self._query(q, blobs)
289323
self.last_query_time = time.time() - start
290324
return self.response, self.blobs
291325
except BaseException as e:
292-
print(e)
326+
logger.critical(e)
293327
raise ConnectionError("ApertureDB disconnected")
294328

295-
def create_new_connection(self):
296-
297-
return Connector(self.host, self.port, session=self.session)
329+
def _renew_session(self):
330+
count = 0
331+
while count < 3:
332+
try:
333+
self._check_session_status()
334+
break
335+
except UnauthorizedException as e:
336+
logger.warn(
337+
f"[Attempt {count + 1} of 3] Failed to refresh token. Details: \r\n{traceback.format_exc(limit=5)}")
338+
time.sleep(1)
339+
count += 1
340+
341+
def create_new_connection(self) -> Connector:
342+
return Connector(self.host, self.port, shared_data=self.shared_data)
298343

299344
def get_last_response_str(self):
300345

aperturedb/__init__.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@
22
import logging
33
import datetime
44
import os
5+
import json
6+
import requests
57

68
logger = logging.getLogger(__name__)
79

8-
__version__ = "0.3.1"
10+
__version__ = "0.3.2"
911

1012
# set log level
1113
logger.setLevel(logging.DEBUG)
1214
formatter = logging.Formatter(
13-
"%(asctime)s : %(levelname)s : %(name)s : %(lineno)d : %(message)s")
15+
"%(asctime)s : %(levelname)s : %(name)s : %(thread)d : %(lineno)d : %(message)s")
1416

1517
log_file_level = logging.getLevelName(os.getenv("LOG_FILE_LEVEL", "WARN"))
1618
log_console_level = logging.getLevelName(
@@ -27,3 +29,16 @@
2729
error_console_handler.setLevel(log_console_level)
2830
error_console_handler.setFormatter(formatter)
2931
logger.addHandler(error_console_handler)
32+
33+
try:
34+
latest_version = json.loads(requests.get(
35+
"https://pypi.org/pypi/aperturedb/json").text)["info"]["version"]
36+
except Exception as e:
37+
logger.warning(
38+
f"Failed to get latest version: {e}. You are using version {__version__}")
39+
latest_version = None
40+
if __version__ != latest_version:
41+
logger.warning(
42+
f"The latest version of aperturedb is {latest_version}. You are using version {__version__}. It is recommended to upgrade.")
43+
print(
44+
f"The latest version of aperturedb is {latest_version}. You are using version {__version__}. It is recommended to upgrade.")

ci.sh

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ update_version() {
2929
fi
3030
echo "Updating version $BUILD_VERSION to $VERSION_BUMP"
3131
# Replace version in __init__.py
32-
printf '%s\n' "%s/__version__.*/__version__ = \"$VERSION_BUMP\"/g" 'x' | ex aperturedb/__init__.py
32+
printf '%s\n' "%s/__version__ = .*/__version__ = \"$VERSION_BUMP\"/g" 'x' | ex aperturedb/__init__.py
3333
printf '%s\n' "%s/version=.*/version=\"$VERSION_BUMP\",/g" 'x' | ex setup.py
3434

3535
# Commit and push version bump
@@ -65,7 +65,7 @@ IMAGE_EXTENSION_WITH_VERSION="-${BRANCH_NAME}:v${BUILD_VERSION}"
6565
if [ $BRANCH_NAME == 'master' ] || [ $BRANCH_NAME == 'main' ]
6666
then
6767
# when merging to master remove branch name
68-
IMAGE_EXTENSION_WITH_VERSION=":v${APP_VERSION}"
68+
IMAGE_EXTENSION_WITH_VERSION=":v${BUILD_VERSION}"
6969
IMAGE_EXTENSION_LATEST=":latest"
7070
fi
7171

@@ -79,11 +79,12 @@ echo "Repository: $DOCKER_REPOSITORY"
7979
# Build notebook image
8080
build_notebook_image(){
8181
NOTEBOOK_IMAGE=$DOCKER_REPOSITORY/aperturedb-notebook${IMAGE_EXTENSION_WITH_VERSION}
82+
LATEST_IMAGE=$DOCKER_REPOSITORY/aperturedb-notebook${IMAGE_EXTENSION_LATEST}
8283
echo "Building image $NOTEBOOK_IMAGE"
83-
docker build -t $NOTEBOOK_IMAGE -f docker/notebook/Dockerfile .
84+
docker build -t $NOTEBOOK_IMAGE -t $LATEST_IMAGE -f docker/notebook/Dockerfile .
8485
if [ -z ${NO_PUSH+x} ]
8586
then
86-
docker push $NOTEBOOK_IMAGE
87+
docker push --all-tags $NOTEBOOK_IMAGE
8788
fi
8889
}
8990

@@ -95,11 +96,12 @@ build_docs_image(){
9596
cp -r ./docs/{*.*,Makefile,_static} docs/docker/build/docs
9697
find examples/ -name *.ipynb | xargs -i cp {} docs/docker/build/examples
9798
DOCS_IMAGE=$DOCKER_REPOSITORY/aperturedb-python-docs${IMAGE_EXTENSION_WITH_VERSION}
99+
LATEST_IMAGE=$DOCKER_REPOSITORY/aperturedb-python-docs${IMAGE_EXTENSION_LATEST}
98100
echo "Building image $DOCS_IMAGE"
99101
docker build -t $DOCS_IMAGE -f docs/docker/Dockerfile .
100102
if [ -z ${NO_PUSH+x} ]
101103
then
102-
docker push $DOCS_IMAGE
104+
docker push --all-tags $DOCS_IMAGE
103105
fi
104106
}
105107

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setuptools.setup(
77
name="aperturedb",
8-
version="0.3.1",
8+
version="0.3.2",
99
description="ApertureDB Client Module",
1010
install_requires=['protobuf>=3.20.0', 'scikit-image', 'image', 'requests', 'boto3',
1111
'opencv-python', 'numpy', 'matplotlib', 'pandas', 'kaggle', 'google-cloud-storage'],

test/conftest.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@
1313
import dbinfo
1414

1515

16-
@pytest.fixture(scope="module")
16+
@pytest.fixture()
1717
def db():
1818
return Connector(
1919
port=dbinfo.DB_PORT,
2020
user=dbinfo.DB_USER,
2121
password=dbinfo.DB_PASSWORD)
2222

2323

24-
@pytest.fixture(scope="module")
24+
@pytest.fixture()
2525
def insert_data_from_csv(db):
2626
def insert_data_from_csv(in_csv_file, rec_count=-1):
2727
file_data_pair = {
@@ -53,7 +53,7 @@ def insert_data_from_csv(in_csv_file, rec_count=-1):
5353
return insert_data_from_csv
5454

5555

56-
@pytest.fixture(scope="module")
56+
@pytest.fixture()
5757
def utils(db):
5858
return Utils(db)
5959

test/test_Session.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ def test_sessionRenew(self):
2020
try:
2121
db = self.create_connection()
2222
# force session token expiry
23-
db.session.session_token_ttl = 1
23+
db.shared_data.session.session_token_ttl = 1
2424
logging.debug("Connected? {0}".format(
2525
"yes" if db.connected else "no"))
2626
logging.debug(
2727
"Session valid? {0}".format(
28-
"yes" if db.session.valid() else "no"))
28+
"yes" if db.shared_data.session.valid() else "no"))
2929
logging.debug("Valid length: {0}".format(
30-
db.session.session_token_ttl))
30+
db.shared_data.session.session_token_ttl))
3131
time.sleep(2)
3232
query = [{
3333
"FindImage": {
@@ -38,6 +38,7 @@ def test_sessionRenew(self):
3838
}]
3939
responses, blobs = db.query(query)
4040
logging.debug(responses)
41-
self.assertTrue(db.session.valid(), "Failed to renew Session")
41+
self.assertTrue(db.shared_data.session.valid(),
42+
"Failed to renew Session")
4243
except Exception:
4344
self.fail("Failed to renew Session")

test/test_torch_connector.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def test_nativeContraints(self, db, utils, images):
6060
assert len(dataset) == utils.count_images()
6161
self.validate_dataset(dataset)
6262

63-
def test_datasetWithMultiprocessing(self, db, utils, images):
63+
def test_datasetWithMultiprocessing(self, db, utils):
6464
query = [{
6565
"FindImage": {
6666
"constraints": {

0 commit comments

Comments
 (0)