t
This commit is contained in:
		| @@ -1,12 +1,12 @@ | ||||
| ## What is happening here? | ||||
|  | ||||
| This "persistence"-module aims to standardize 2 things: | ||||
| * the creation of a common set of variables that survives a potential (let's face it, likely) crash | ||||
| * advanced logging and analytics | ||||
|  | ||||
| ### Common variables | ||||
| These are saved as a json file and are handled internally as a dict. Each change in the dict triggers a write to the file. | ||||
|  | ||||
|  | ||||
| ### Logging | ||||
| ## What is happening here? | ||||
|  | ||||
| This "persistence"-module aims to standardize 2 things: | ||||
| * the creation of a common set of variables that survives a potential (let's face it, likely) crash | ||||
| * advanced logging and analytics | ||||
|  | ||||
| ### Common variables | ||||
| These are saved as a json file and are handled internally as a dict. Each change in the dict triggers a write to the file. | ||||
|  | ||||
|  | ||||
| ### Logging | ||||
| A chunky sqlite-db which periodically gets new entries. From all modules. Ideally this db is then visualized through grafana. WIP | ||||
| @@ -1,71 +1,55 @@ | ||||
| from peewee import * | ||||
| # from playhouse.pool import PooledMySQLDatabase | ||||
| from playhouse.shortcuts import ReconnectMixin | ||||
| import logging | ||||
| logger = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
|  | ||||
| db = DatabaseProxy() | ||||
| # set the nature of the db at runtime | ||||
|  | ||||
| class ReconnectDataBase(ReconnectMixin, MySQLDatabase): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class DBModel(Model): | ||||
|     # specific to the above DB | ||||
|     class Meta: | ||||
|         database = db | ||||
|  | ||||
|     def save(self): | ||||
|         # fail-safe writing of the db-object. Usually threaded because the caller is threaded | ||||
|         try: | ||||
|             # db.connect() | ||||
|             super().save() | ||||
|             # db.close() | ||||
|         except Exception as e: | ||||
|             logger.error("Could not write to db. Dropping content of {}".format(self.__class__.__name__)) | ||||
|             logger.error(e) | ||||
|             # db.atomic().rollback() | ||||
|  | ||||
|     # def get(self, *query, **filters): | ||||
|     #     try: | ||||
|     #         return super().get(*query, **filters) | ||||
|     #     except Exception as e: | ||||
|     #         logger.error("Error while executing get: {}".format(e)) | ||||
|     #         print(query, filters) | ||||
|  | ||||
|  | ||||
| class Metric(DBModel): | ||||
|     time = DateTimeField() | ||||
|          | ||||
|  | ||||
| ### Actual metrics: | ||||
|  | ||||
| class SensorMetric(Metric): | ||||
|     # this is a continuous metric | ||||
|     temperature = IntegerField() | ||||
|     humidity = IntegerField() | ||||
|     luminosity = IntegerField() | ||||
|     default = {"temperature": 100, "humidity": 100, "luminosity": 100} | ||||
|  | ||||
|  | ||||
|  | ||||
| class ChatMetric(Metric): | ||||
|     read = BooleanField() | ||||
|     send = BooleanField() | ||||
|     execute = BooleanField() | ||||
|     default = {"read": False, "send": False, "execute": False} | ||||
|  | ||||
|  | ||||
| class ErrorMetric(Metric): | ||||
|     # same as above | ||||
|     error = TextField() | ||||
|     default = {"error": "SQL connection broke off"} | ||||
|  | ||||
|  | ||||
| class List(DBModel): | ||||
|     name = CharField(unique=True) | ||||
|     content = TextField() # unlimited length, use to serialise list into | ||||
|     default = {"content": "SQL connection broke off"} | ||||
| from datetime import datetime | ||||
| from peewee import * | ||||
| import logging | ||||
| import json | ||||
|  | ||||
| logger = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| def create_tables(db): | ||||
|     db.create_tables([SensorMetric, ChatMetric, ErrorMetric, List]) | ||||
|  | ||||
| db = DatabaseProxy() | ||||
| # set the nature of the db at runtime | ||||
|  | ||||
|  | ||||
| class DBModel(Model): | ||||
|     # specific to the above DB | ||||
|     class Meta: | ||||
|         database = db | ||||
|  | ||||
| class Metric(DBModel): | ||||
|     time = DateTimeField(default = datetime.now()) | ||||
|          | ||||
|  | ||||
|  | ||||
| ### Actual metrics: | ||||
|  | ||||
| class SensorMetric(Metric): | ||||
|     # this is a continuous metric | ||||
|     temperature = IntegerField() | ||||
|     humidity = IntegerField() | ||||
|     luminosity = IntegerField() | ||||
|  | ||||
|  | ||||
| class ChatMetric(Metric): | ||||
|     read = BooleanField() | ||||
|     send = BooleanField() | ||||
|     execute = BooleanField() | ||||
|  | ||||
|  | ||||
| class ErrorMetric(Metric): | ||||
|     # same as above | ||||
|     error = TextField() | ||||
|  | ||||
|  | ||||
| class List(DBModel): | ||||
|     name = CharField(unique=True) | ||||
|     content = TextField() # unlimited length, use to serialise list into | ||||
|  | ||||
|     @property | ||||
|     def content_as_list(self): | ||||
|         return json.loads(self.content) | ||||
|      | ||||
|     def set_content(self, list_content): | ||||
|         self.content = json.dumps(list_content) | ||||
| @@ -1,87 +1,87 @@ | ||||
| import json | ||||
| import os | ||||
|  | ||||
|  | ||||
| class PersistentDict(dict): | ||||
|     """Extended dict that writes its content to a file every time a value is changed""" | ||||
|  | ||||
|     def __init__(self, file_name, *args, **kwargs): | ||||
|         """initialization of the dict and of the required files""" | ||||
|         super().__init__(*args, **kwargs) | ||||
|  | ||||
|         self.path = file_name | ||||
|         self.last_action = "" | ||||
|         try: | ||||
|             self.read_dict() | ||||
|         except: | ||||
|             with open(self.path, "a") as f: | ||||
|                 f.write("{}") | ||||
|          | ||||
|  | ||||
|     ## helper - functions | ||||
|     def write_dict(self): | ||||
|         with open(self.path, "w") as f: | ||||
|             json.dump(self, f) | ||||
|         self.last_action = "w" | ||||
|  | ||||
|     def read_dict(self): | ||||
|         with open(self.path) as f: | ||||
|             tmp = dict(json.load(f)) | ||||
|             for key in tmp: | ||||
|                 super().__setitem__(key, tmp[key]) | ||||
|         self.last_action = "r" | ||||
|  | ||||
|  | ||||
|     ## extended dictionary - logic | ||||
|     def __setitem__(self, key, value): | ||||
|         if self.last_action != "r": | ||||
|             self.read_dict() | ||||
|         # not sure if the step to read is necessary, but I'll keep it for safety | ||||
|         super().__setitem__(key,value) | ||||
|         self.write_dict() # writes 'self' to a json file. | ||||
|  | ||||
|     def __getitem__(self, key): | ||||
|         if self.last_action != "r": | ||||
|             self.read_dict() | ||||
|          | ||||
|         ret_val = super().__getitem__(key) | ||||
|         if type(ret_val) != int and type(ret_val) != str: | ||||
|             ret_val = create_struct(type(ret_val), key, self, ret_val) | ||||
|  | ||||
|         return ret_val | ||||
|      | ||||
|     def clear(self): | ||||
|         super().clear() | ||||
|         self.write_dict() | ||||
|         return {} | ||||
|  | ||||
|  | ||||
|  | ||||
| def create_struct(struct_type, own_name, parent_name, *args, **kwargs): | ||||
|     class HookedStruct(struct_type): | ||||
|  | ||||
|         def __init__(self, own_name, parent_name, *args, **kwargs): | ||||
|             super().__init__(*args, **kwargs) | ||||
|             self.name = own_name | ||||
|             self.parent = parent_name | ||||
|          | ||||
|         def __setitem__(self, *args, **kwargs): | ||||
|             super().__setitem__(*args, **kwargs) | ||||
|             self.parent.__setitem__(self.name, self) | ||||
|  | ||||
|         def __getitem__(self, *args, **kwargs): | ||||
|             ret_val = super().__getitem__(*args, **kwargs) | ||||
|             if type(ret_val) != int and type(ret_val) != str: | ||||
|                 ret_val = create_struct(type(ret_val), args[0], self, ret_val) | ||||
|             return ret_val | ||||
|  | ||||
|         def pop(self, *args): | ||||
|             retvalue = super().pop(*args) | ||||
|             self.parent.__setitem__(self.name, self) | ||||
|             return retvalue | ||||
|  | ||||
|         def append(self, *args): | ||||
|             super().append(*args) | ||||
|             self.parent.__setitem__(self.name, self) | ||||
|  | ||||
|     return HookedStruct(own_name, parent_name, *args, **kwargs) | ||||
| import json | ||||
| import os | ||||
|  | ||||
|  | ||||
| class PersistentDict(dict): | ||||
|     """Extended dict that writes its content to a file every time a value is changed""" | ||||
|  | ||||
|     def __init__(self, file_name, *args, **kwargs): | ||||
|         """initialization of the dict and of the required files""" | ||||
|         super().__init__(*args, **kwargs) | ||||
|  | ||||
|         self.path = file_name | ||||
|         self.last_action = "" | ||||
|         try: | ||||
|             self.read_dict() | ||||
|         except: | ||||
|             with open(self.path, "a") as f: | ||||
|                 f.write("{}") | ||||
|          | ||||
|  | ||||
|     ## helper - functions | ||||
|     def write_dict(self): | ||||
|         with open(self.path, "w") as f: | ||||
|             json.dump(self, f) | ||||
|         self.last_action = "w" | ||||
|  | ||||
|     def read_dict(self): | ||||
|         with open(self.path) as f: | ||||
|             tmp = dict(json.load(f)) | ||||
|             for key in tmp: | ||||
|                 super().__setitem__(key, tmp[key]) | ||||
|         self.last_action = "r" | ||||
|  | ||||
|  | ||||
|     ## extended dictionary - logic | ||||
|     def __setitem__(self, key, value): | ||||
|         if self.last_action != "r": | ||||
|             self.read_dict() | ||||
|         # not sure if the step to read is necessary, but I'll keep it for safety | ||||
|         super().__setitem__(key,value) | ||||
|         self.write_dict() # writes 'self' to a json file. | ||||
|  | ||||
|     def __getitem__(self, key): | ||||
|         if self.last_action != "r": | ||||
|             self.read_dict() | ||||
|          | ||||
|         ret_val = super().__getitem__(key) | ||||
|         if type(ret_val) != int and type(ret_val) != str: | ||||
|             ret_val = create_struct(type(ret_val), key, self, ret_val) | ||||
|  | ||||
|         return ret_val | ||||
|      | ||||
|     def clear(self): | ||||
|         super().clear() | ||||
|         self.write_dict() | ||||
|         return {} | ||||
|  | ||||
|  | ||||
|  | ||||
| def create_struct(struct_type, own_name, parent_name, *args, **kwargs): | ||||
|     class HookedStruct(struct_type): | ||||
|  | ||||
|         def __init__(self, own_name, parent_name, *args, **kwargs): | ||||
|             super().__init__(*args, **kwargs) | ||||
|             self.name = own_name | ||||
|             self.parent = parent_name | ||||
|          | ||||
|         def __setitem__(self, *args, **kwargs): | ||||
|             super().__setitem__(*args, **kwargs) | ||||
|             self.parent.__setitem__(self.name, self) | ||||
|  | ||||
|         def __getitem__(self, *args, **kwargs): | ||||
|             ret_val = super().__getitem__(*args, **kwargs) | ||||
|             if type(ret_val) != int and type(ret_val) != str: | ||||
|                 ret_val = create_struct(type(ret_val), args[0], self, ret_val) | ||||
|             return ret_val | ||||
|  | ||||
|         def pop(self, *args): | ||||
|             retvalue = super().pop(*args) | ||||
|             self.parent.__setitem__(self.name, self) | ||||
|             return retvalue | ||||
|  | ||||
|         def append(self, *args): | ||||
|             super().append(*args) | ||||
|             self.parent.__setitem__(self.name, self) | ||||
|  | ||||
|     return HookedStruct(own_name, parent_name, *args, **kwargs) | ||||
|   | ||||
| @@ -1,116 +0,0 @@ | ||||
| from . import models | ||||
| from peewee import * | ||||
| # from playhouse.pool import PooledMySQLDatabase | ||||
| from playhouse.shortcuts import ReconnectMixin | ||||
| import inspect | ||||
|  | ||||
| from . import keys | ||||
| dbk = keys.db_keys | ||||
|  | ||||
|  | ||||
| class ReconnectDataBase(ReconnectMixin, MySQLDatabase): | ||||
|     pass | ||||
|  | ||||
|  | ||||
|  | ||||
| class DBConnector: | ||||
|     """Create a connection to a remote database and log some quantities that will be visualized otherwhere""" | ||||
|     def __init__(self): | ||||
|         self.db = models.db | ||||
|          | ||||
|         self.sensors = models.SensorMetric | ||||
|         self.chats = models.ChatMetric | ||||
|         self.errors = models.ErrorMetric | ||||
|         self.lists = models.List | ||||
|  | ||||
|         self.create_tables() | ||||
|  | ||||
|     def create_tables(self): | ||||
|         self.db.create_tables([self.sensors, self.chats, self.errors, self.lists]) | ||||
|  | ||||
|      | ||||
|  | ||||
| class DataBaseConnector: | ||||
|     def __init__(self) -> None: | ||||
|         self.db_object = models.ReconnectDataBase( | ||||
|             dbk["name"], | ||||
|             user=dbk["username"], | ||||
|             password=dbk["password"], | ||||
|             host=dbk["url"], | ||||
|             port=dbk["port"], | ||||
|             autorollback=True | ||||
|         ) | ||||
|         models.db.initialize(self.db_object) | ||||
|  | ||||
|         # self.sensors = models.SensorMetric | ||||
|         # self.chats = models.ChatMetric | ||||
|         # self.errors = models.ErrorMetric | ||||
|         # self.lists = models.List | ||||
|         ## Set as property methods instead | ||||
|  | ||||
|         self.db_object.create_tables([self.sensors, self.chats, self.errors, self.lists]) | ||||
|      | ||||
|     @property | ||||
|     def sensors(self): | ||||
|         self.connect_first() | ||||
|         return models.SensorMetric | ||||
|      | ||||
|     @property | ||||
|     def chats(self): | ||||
|         self.connect_first() | ||||
|         return models.ChatMetric | ||||
|      | ||||
|     @property | ||||
|     def errors(self): | ||||
|         self.connect_first() | ||||
|         return models.ErrorMetric | ||||
|  | ||||
|     @property | ||||
|     def lists(self): | ||||
|         self.connect_first() | ||||
|         return models.List | ||||
|  | ||||
|     def connect_first(self): | ||||
|         # if self.db_object.is_closed(): | ||||
|         #    self.db_object.connect() | ||||
|         self.db_object.connect(reuse_if_open=True) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| # def auto_reconnect(func, *args, **kwargs): | ||||
| #     return func | ||||
|  | ||||
| # def classwide_decorator(decorator): | ||||
| #     def decorate(cls): | ||||
| #         for attr in inspect.getmembers(cls, inspect.ismethod): # there's propably a better way to do this | ||||
| #             # TODO: filter init | ||||
| #             print(attr) | ||||
| #             if callable(getattr(cls, attr)): | ||||
| #                 setattr(cls, attr, decorator(getattr(cls, attr))) | ||||
| #         return cls | ||||
| #     return decorate | ||||
|  | ||||
| # # apply auto_reconnect to every method so that every method first checks the db connection and reconnects if necessary | ||||
| # @classwide_decorator(auto_reconnect)  | ||||
| # class DataBaseConnector(ReconnectMixin, MySQLDatabase): | ||||
| #     def __init__(self, *args, **kwargs): | ||||
| #         super().__init__( | ||||
| #             dbk["name"], | ||||
| #             user=dbk["username"], | ||||
| #             password=dbk["password"], | ||||
| #             host=dbk["url"], | ||||
| #             port=dbk["port"], | ||||
| #             autorollback=True, | ||||
| #             *args, **kwargs) | ||||
|  | ||||
| #         models.db.initialize(self) | ||||
| #         self.sensors = models.SensorMetric | ||||
| #         self.chats = models.ChatMetric | ||||
| #         self.errors = models.ErrorMetric | ||||
| #         self.lists = models.List | ||||
|  | ||||
| #         self.create_tables([self.sensors, self.chats, self.errors, self.lists]) | ||||
|  | ||||
| #     def m1(self): pass | ||||
| #     def m2(self, x): pass | ||||
		Reference in New Issue
	
	Block a user
	 Remy Moll
					Remy Moll