from flask_sqlalchemy import SQLAlchemy from sqlalchemy import exc from settings import * db = SQLAlchemy() __all__ = ['db', 'save_row', 'delete_row', 'User', 'Camera', 'Download' ] # # helper function to reduce repeated code # def commit_or_rollback(db, obj): try: db.session.add(obj) db.session.commit() except exc.IntegrityError as e: print(f"caught an exception {e}") db.session.rollback() except exc.OperationalError as e: print(f"caught an exception {e}") db.session.rollback() def delete_or_rollback(db, obj): try: db.session.delete(obj) db.session.commit() except exc.IntegrityError as e: print(f"caught an exception {e}") db.session.rollback() except exc.OperationalError as e: print(f"caught an exception {e}") db.session.rollback() def save_row(obj): commit_or_rollback(db, obj) def delete_row(obj): delete_or_rollback(db, obj) # trying out making my own BaseModel, follow this blogpost # https://dev.to/chidioguejiofor/making-sqlalchemy-models-simpler-by-creating-a-basemodel-3m9c class BaseModel(db.Model): __abstract__ = True id = db.Column(db.Integer, primary_key=True) created = db.Column(db.DateTime, server_default=db.func.now()) updated = db.Column(db.DateTime, server_default=db.func.now(), server_onupdate=db.func.now()) def before_save(self, *args, **kwargs): pass def after_save(self, *args, **kwargs): pass def save(self, commit=True): self.before_save() db.session.add(self) if commit: try: db.session.commit() except Exception as e: db.session.rollback() raise e self.after_save() def before_update(self, *args, **kwargs): pass def after_update(self, *args, **kwargs): pass def update(self, *args, **kwargs): self.before_update(*args, **kwargs) db.session.commit() self.after_update(*args, **kwargs) def delete(self, commit=True): db.session.delete(self) if commit: db.session.commit() @classmethod def eager(cls, *args): cols = [orm.joinedload(arg) for arg in args] return cls.query.options(*cols) @classmethod def before_bulk_create(cls, iterable, *args, **kwargs): pass @classmethod def after_bulk_create(cls, model_objs, *args, **kwargs): pass @classmethod def bulk_create(cls, iterable, *args, **kwargs): cls.before_bulk_create(iterable, *args, **kwargs) model_objs = [] for data in iterable: if not isinstance(data, cls): data = cls(**data) model_objs.append(data) db.session.bulk_save_objects(model_objs) if kwargs.get('commit', True) is True: db.session.commit() cls.after_bulk_create(model_objs, *args, **kwargs) return model_objs @classmethod def bulk_create_or_none(cls, iterable, *args, **kwargs): try: return cls.bulk_create(iterable, *args, **kwargs) except exc.IntegrityError as e: db.session.rollback() return None class User(BaseModel): ''' The User object stores basic data from EagleEye API along with cameras they have access to, and stores the access/refresh tokens. ''' account_id = db.Column(db.String(8), nullable=True) user_id = db.Column(db.String(8), nullable=True) email = db.Column(db.String(), nullable=True) timezone = db.Column(db.String(), nullable=True) access_token = db.Column(db.String(), nullable=True) refresh_token = db.Column(db.String(), nullable=True) last_login = db.Column(db.DateTime, nullable=True) cameras = db.relationship('Camera', backref=db.backref('user', lazy=True)) def __repr__(self): return f"[User] {email}" def to_dict(self): return { "account_id": self.account_id, "user_id": self.user_id, "email": self.email, "timezone": self.timezone, "access_token": self.access_token, "refresh_token": self.refresh_token, "last_login": self.last_login, "cameras": self.cameras } class Camera(BaseModel): ''' The Camera object holds the list of recordings (Downloads) and some basics about the device ''' device_id = db.Column(db.String(8), nullable=True) timezone = db.Column(db.String(), nullable=True) name = db.Column(db.String(), nullable=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True) downloads = db.relationship('Download', backref=db.backref('camera', lazy=True)) def __repr__(self): return f"[Camera] {device_id} {name}" def get_newest_download(self, status=None): return None def get_oldest_download(self, status=None): return None class Download(BaseModel): ''' Downloads are the actual recording that should be downloaded. It keeps its own state/progress/errors so it can be reported on later. Downloads belong to a camera which by extension belongs to a user ''' url = db.Column(db.String(), nullable=True) camera_id = db.Column(db.Integer, db.ForeignKey('camera.id'), nullable=True) start_timestamp = db.Column(db.DateTime, nullable=True) end_timestamp = db.Column(db.DateTime, nullable=True) status = db.Column(db.String(), nullable=True) error = db.Column(db.String(), nullable=True) def __repr__(self): return f"[Download] {id} {camera_id} {status} {error}"