# Path: minette/serializer.py
# def dumps(obj, **kwargs):
#     """
#     Encode object/dict to JSON
# 
#     Parameters
#     ----------
#     obj : object
#         Object to encode
# 
#     Returns
#     -------
#     s : str
#         JSON string
#     """
#     if obj is None:
#         return ""
#     d = dumpd(obj)
#     return json.dumps(d, default=_encode_datetime, **kwargs)
# 
# def loads(s, obj_cls=None, **kwargs):
#     """
#     Decode JSON to dict/object
# 
#     Parameters
#     ----------
#     s : str
#         JSON string to decode
#     obj_cls : type, default None
#         Class of object to convert. If None, convert to dict
# 
#     Returns
#     -------
#     obj : object
#         Instance of obj_cls
#     """
#     if s is None or s == "":
#         return None
#     d = json.loads(s, object_hook=_decode_datetime, **kwargs)
#     if obj_cls is None:
#         return d
#     else:
#         return loadd(d, obj_cls)
# 
# Path: minette/models/topic.py
# class Topic(Serializable):
#     """
#     Topic
# 
#     Attributes
#     ----------
#     name : str
#         Name of topic
#     status : str
#         Status of topic
#     is_new : bool
#         True if topic starts at this turn
#     keep_on : bool
#         True to keep this topic at next turn
#     previous : minette.Topic
#         Previous topic object
#     priority : int
#         Priority of topic
#     is_changed : bool
#         True if topic is changed at this turn
#     """
#     def __init__(self):
#         self.name = ""
#         self.status = ""
#         self.is_new = False
#         self.keep_on = False
#         self.previous = None
#         self.priority = Priority.Normal
# 
#     @property
#     def is_changed(self):
#         if self.previous and self.previous.name == self.name:
#             return False
#         else:
#             return True
# 
# Path: minette/models/context.py
# class Context(Serializable):
#     """
#     Context
# 
#     Attributes
#     ----------
#     channel : str
#         Channel
#     channel_user_id : str
#         Channel user ID
#     timestamp : datetime
#         Timestamp
#     is_new : bool
#         True if context is created at this turn
#     topic : Topic
#         Current topic
#     data : dict
#         Data slots
#     error : dict
#         Error info
#     """
#     def __init__(self, channel=None, channel_user_id=None):
#         """
#         Parameters
#         ----------
#         channel : str, default None
#             Channel
#         channel_user_id : str, default None
#             Channel user ID
#         """
#         self.channel = channel
#         self.channel_user_id = \
#             channel_user_id if isinstance(channel_user_id, str) else ""
#         self.timestamp = None
#         self.is_new = True
#         self.topic = Topic()
#         self.data = {}
#         self.error = {}
# 
#     def reset(self, keep_data=False):
#         """
#         Backup to previous topic and remove data
# 
#         Parameters
#         ----------
#         keep_data : bool, default False
#             Keep context data to next turn
#         """
#         # backup previous topic
#         self.topic.previous = None
#         self.topic.previous = deepcopy(self.topic)
#         # remove data if topic not keep_on
#         if not self.topic.keep_on:
#             self.topic.name = ""
#             self.topic.status = ""
#             self.topic.priority = Priority.Normal
#             self.data = self.data if keep_data else {}
#             self.error = {}
# 
#     def set_error(self, ex, info=None):
#         """
#         Set error info
# 
#         Parameters
#         ----------
#         ex : Exception
#             Exception
#         info : dict, default None
#             More information for debugging
#         """
#         self.error = {
#             "exception": str(ex),
#             "traceback": traceback.format_exc(),
#             "info": info if info else {}}
# 
#     @classmethod
#     def _types(cls):
#         return {
#             "topic": Topic
#         }


from abc import ABC, abstractmethod
from logging import getLogger
from datetime import datetime
from pytz import timezone as tz
from ..serializer import dumps, loads
from ..models import Context, Topic
import traceback
            Channel
        channel_user_id : str
            Channel user ID
        connection : Connection
            Connection

        Returns
        -------
        context : minette.Context
            Context for channel and channel_user_id
        """
        context = Context(channel, channel_user_id)
        context.timestamp = datetime.now(self.timezone)
        if not channel_user_id:
            return context
        try:
            cursor = connection.cursor()
            cursor.execute(
                self.sqls["get_context"], (channel, channel_user_id))
            row = cursor.fetchone()
            if row is not None:
                # convert to dict
                if isinstance(row, dict):
                    record = row
                else:
                    record = dict(
                        zip([column[0] for column in cursor.description], row))
                # convert type
                record["topic_previous"] = \
                    loads(record["topic_previous"])

# Path: minette/serializer.py
# def dumps(obj, **kwargs):
#     """
#     Encode object/dict to JSON
# 
#     Parameters
#     ----------
#     obj : object
#         Object to encode
# 
#     Returns
#     -------
#     s : str
#         JSON string
#     """
#     if obj is None:
#         return ""
#     d = dumpd(obj)
#     return json.dumps(d, default=_encode_datetime, **kwargs)
# 
# def loads(s, obj_cls=None, **kwargs):
#     """
#     Decode JSON to dict/object
# 
#     Parameters
#     ----------
#     s : str
#         JSON string to decode
#     obj_cls : type, default None
#         Class of object to convert. If None, convert to dict
# 
#     Returns
#     -------
#     obj : object
#         Instance of obj_cls
#     """
#     if s is None or s == "":
#         return None
#     d = json.loads(s, object_hook=_decode_datetime, **kwargs)
#     if obj_cls is None:
#         return d
#     else:
#         return loadd(d, obj_cls)
# 
# Path: minette/models/topic.py
# class Topic(Serializable):
#     """
#     Topic
# 
#     Attributes
#     ----------
#     name : str
#         Name of topic
#     status : str
#         Status of topic
#     is_new : bool
#         True if topic starts at this turn
#     keep_on : bool
#         True to keep this topic at next turn
#     previous : minette.Topic
#         Previous topic object
#     priority : int
#         Priority of topic
#     is_changed : bool
#         True if topic is changed at this turn
#     """
#     def __init__(self):
#         self.name = ""
#         self.status = ""
#         self.is_new = False
#         self.keep_on = False
#         self.previous = None
#         self.priority = Priority.Normal
# 
#     @property
#     def is_changed(self):
#         if self.previous and self.previous.name == self.name:
#             return False
#         else:
#             return True
# 
# Path: minette/models/context.py
# class Context(Serializable):
#     """
#     Context
# 
#     Attributes
#     ----------
#     channel : str
#         Channel
#     channel_user_id : str
#         Channel user ID
#     timestamp : datetime
#         Timestamp
#     is_new : bool
#         True if context is created at this turn
#     topic : Topic
#         Current topic
#     data : dict
#         Data slots
#     error : dict
#         Error info
#     """
#     def __init__(self, channel=None, channel_user_id=None):
#         """
#         Parameters
#         ----------
#         channel : str, default None
#             Channel
#         channel_user_id : str, default None
#             Channel user ID
#         """
#         self.channel = channel
#         self.channel_user_id = \
#             channel_user_id if isinstance(channel_user_id, str) else ""
#         self.timestamp = None
#         self.is_new = True
#         self.topic = Topic()
#         self.data = {}
#         self.error = {}
# 
#     def reset(self, keep_data=False):
#         """
#         Backup to previous topic and remove data
# 
#         Parameters
#         ----------
#         keep_data : bool, default False
#             Keep context data to next turn
#         """
#         # backup previous topic
#         self.topic.previous = None
#         self.topic.previous = deepcopy(self.topic)
#         # remove data if topic not keep_on
#         if not self.topic.keep_on:
#             self.topic.name = ""
#             self.topic.status = ""
#             self.topic.priority = Priority.Normal
#             self.data = self.data if keep_data else {}
#             self.error = {}
# 
#     def set_error(self, ex, info=None):
#         """
#         Set error info
# 
#         Parameters
#         ----------
#         ex : Exception
#             Exception
#         info : dict, default None
#             More information for debugging
#         """
#         self.error = {
#             "exception": str(ex),
#             "traceback": traceback.format_exc(),
#             "info": info if info else {}}
# 
#     @classmethod
#     def _types(cls):
#         return {
#             "topic": Topic
#         }


# Path: minette/datastore/contextstore.py
from abc import ABC, abstractmethod
from logging import getLogger
from datetime import datetime
from pytz import timezone as tz
from ..serializer import dumps, loads
from ..models import Context, Topic
import traceback

            Channel
        channel_user_id : str
            Channel user ID
        connection : Connection
            Connection

        Returns
        -------
        context : minette.Context
            Context for channel and channel_user_id
        """
        context = Context(channel, channel_user_id)
        context.timestamp = datetime.now(self.timezone)
        if not channel_user_id:
            return context
        try:
            cursor = connection.cursor()
            cursor.execute(
                self.sqls["get_context"], (channel, channel_user_id))
            row = cursor.fetchone()
            if row is not None:
                # convert to dict
                if isinstance(row, dict):
                    record = row
                else:
                    record = dict(
                        zip([column[0] for column in cursor.description], row))
                # convert type
                record["topic_previous"] = \
                    loads(record["topic_previous"])

                record["data"] = loads(record["data"])