Skip to content

Commit 7967eb9

Browse files
committed
Merge branch 'main' into hugging
2 parents 5330687 + 1404092 commit 7967eb9

File tree

2 files changed

+141
-6
lines changed

2 files changed

+141
-6
lines changed

server/storage/mongo/__init__.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,30 @@
99
logger = init_logger(__name__)
1010

1111

12-
default_options = {"name": "mongo", "database": "conserver", "collection_name": "vcons"}
12+
default_options = {
13+
"name": "mongo",
14+
"database": "conserver",
15+
"collection": "vcons",
16+
"url": "mongodb://localhost:27017/"
17+
}
1318

19+
def convert_date_to_mongo_date(date_str) -> datetime:
20+
"""
21+
Convert ISO 8601 date string to datetime object.
22+
Handles both 'Z' UTC indicator and explicit timezone offsets like '+00:00'.
23+
"""
24+
try:
25+
# Try the format with 'Z' at the end
26+
return datetime.strptime(date_str, "%Y-%m-%dT%H:%M:%S.%fZ")
27+
except ValueError:
28+
try:
29+
# Try the format with timezone offset like '+00:00'
30+
# Python's %z doesn't handle the colon in "+00:00", so we need to use dateutil
31+
from dateutil import parser
32+
return parser.parse(date_str)
33+
except Exception as e:
34+
logger.error(f"Failed to parse date: {date_str}, error: {e}")
35+
raise
1436

1537
def prepare_vcon_for_mongo(vcon: Vcon) -> Dict[str, Any]:
1638
"""
@@ -25,10 +47,9 @@ def prepare_vcon_for_mongo(vcon: Vcon) -> Dict[str, Any]:
2547
logger.debug(f"Preparing vCon {vcon.uuid} for MongoDB storage")
2648
clean_vcon = vcon.to_dict()
2749
clean_vcon["_id"] = vcon.uuid
28-
clean_vcon["created_at"] = datetime.fromisoformat(clean_vcon["created_at"])
50+
clean_vcon["created_at"] = convert_date_to_mongo_date(clean_vcon["created_at"])
2951
for dialog in clean_vcon["dialog"]:
30-
dialog["start"] = datetime.fromisoformat(dialog["start"])
31-
logger.debug(f"Successfully prepared vCon {vcon.uuid} for MongoDB storage")
52+
dialog["start"] = convert_date_to_mongo_date(dialog["start"])
3253
return clean_vcon
3354

3455

@@ -63,8 +84,8 @@ def save(
6384

6485
vcon_redis = VconRedis()
6586
vcon = vcon_redis.get_vcon(vcon_uuid)
66-
logger.debug(f"Retrieved vCon {vcon_uuid} from Redis")
67-
87+
if vcon is None:
88+
raise ValueError(f"vCon with UUID {vcon_uuid} not found in Redis")
6889
db = client[opts["database"]]
6990
collection = db[opts["collection"]]
7091

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import os, sys, json, uuid6
2+
from pathlib import Path
3+
from datetime import datetime
4+
from dotenv import load_dotenv
5+
from spaceandtime import SpaceAndTime, SXTTable # pip3 install spaceandtime
6+
7+
# for testing in-place: add parent directories to path to support local imports
8+
# remove this next line once fully integerated (or not, whatever):
9+
for i in range(0,4): sys.path.append(str(Path(__file__).parents[i]))
10+
from lib.logging_utils import init_logger
11+
from server.lib.vcon_redis import VconRedis
12+
13+
14+
# Probably merge these Envars into one of the existing config files
15+
load_dotenv()
16+
SXT_API_KEY = os.getenv("SXT_API_KEY") # Check out: https://docs.spaceandtime.io/docs/api-key
17+
SXT_VCON_TABLENAME = os.getenv("SXT_VCON_TABLENAME") # Provided by SXT
18+
SXT_VCON_TABLE_WRITE_BISCUIT = os.getenv("SXT_VCON_TABLE_WRITE_BISCUI") # Provided by SXT
19+
20+
21+
# Initialize the logger and default VCON options:
22+
logger = init_logger(__name__)
23+
default_options = {"name": "spaceandtime"}
24+
25+
# Define the SXT object once. Can also move this into the save/get functions
26+
sxt = SpaceAndTime(api_key=SXT_API_KEY, authenticate=True, logger=logger )
27+
28+
29+
30+
def save(
31+
vcon_uuid,
32+
opts=default_options
33+
):
34+
logger.info("Starting the spaceandtime storage for vCon: %s", vcon_uuid)
35+
try:
36+
vcon_redis = VconRedis()
37+
38+
# GET VCON, EITHER REAL FROM REDIS OR FOR TESTING
39+
if vcon_uuid == 'test': # generate a test record:
40+
vcon = {"uuid": uuid6.uuid6(),
41+
"vcon": "test",
42+
"created_at": datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
43+
"subject": "test",
44+
"vcon_json": json.dumps(opts) }
45+
else:
46+
vcon = vcon_redis.get_vcon(vcon_uuid)
47+
48+
# ENSURE CONNECTION TO SPACE AND TIME IS STILL VALID:
49+
if sxt.user.access_expired:
50+
success, response = sxt.authenticate()
51+
if not success: raise Exception(response)
52+
53+
tbl = SXTTable(name=SXT_VCON_TABLENAME, SpaceAndTime_parent=sxt) # inherit user/logging/etc. from sxt object
54+
tbl.biscuits.append(SXT_VCON_TABLE_WRITE_BISCUIT)
55+
56+
# MAKE SURE VCON IS WELL FORMED:
57+
for col in vcon.keys():
58+
if col.upper() not in tbl.columns.keys():
59+
raise Exception(f"VCON object doesn't have a corresponding column in table:{SXT_VCON_TABLENAME}, column:{str(col).upper()}")
60+
61+
# INSERT ROW INTO SPACE AND TIME
62+
success, response = tbl.insert.list_of_dicts(list_of_dicts=[dict(vcon)]) # this can insert many rows, if more efficient
63+
64+
# LOG RESULTS:
65+
if success: logger.info("Finished the spaceandtime storage for vCon: %s", vcon_uuid)
66+
else: raise Exception(response)
67+
68+
return vcon['uuid']
69+
70+
except Exception as e:
71+
logger.error(f"spaceandtime storage plugin: failed to insert vCon: {vcon_uuid}, error: {e} ")
72+
73+
74+
75+
def get(
76+
vcon_uuid,
77+
opts=default_options,
78+
):
79+
logger.info("Starting the spaceandtime storage get for vCon: %s", vcon_uuid)
80+
try:
81+
82+
# CONNECT TO SPACE AND TIME
83+
if sxt.user.access_expired:
84+
success, response = sxt.authenticate()
85+
if not success: raise Exception(response)
86+
87+
tbl = SXTTable(name=SXT_VCON_TABLENAME, SpaceAndTime_parent=sxt)
88+
tbl.biscuits.append(SXT_VCON_TABLE_WRITE_BISCUIT)
89+
90+
success, response = tbl.select(sql_text=f"select * from {tbl.table_name} where uuid='{vcon_uuid}' ")
91+
if not success: raise Exception(response)
92+
93+
# looks like the postgres plugin returns only the 'vcon_json' column, so we will too:
94+
return None if response == [] else json.loads(response[0]['VCON_JSON'])
95+
96+
except Exception as e:
97+
logger.error(
98+
f"Postgres storage plugin: failed to get vCon: {vcon_uuid}, error: {e} "
99+
)
100+
101+
102+
103+
if __name__ == "__main__": # lightweight unit testing
104+
105+
# genearate a new test vcon
106+
uuid = save('test')
107+
108+
# retrieve the VCON we just created
109+
data = get(uuid)
110+
print(data)
111+
112+
# retrieve a non-existant VCON (aka NONE)
113+
data = get('nope')
114+
print(data)

0 commit comments

Comments
 (0)