Esempio n. 1
0
class VersionedDocument:

    versioned_document_uuid = UUID()
    versioned_document_version = String()
    versioned_document = Function(fget='get_versioned_document',
                                  fset='set_versioned_document',
                                  fdel='del_versioned_document')

    def get_versioned_document(self):
        Document = self.registry.Attachment.Document
        query = Document.query()
        query = query.filter(
            Document.uuid == self.versioned_document_uuid,
            Document.version == self.versioned_document_version)
        return query.one_or_none()

    def set_versioned_document(self, document):
        if document.uuid is None:
            raise NoneValueException("Uuid value is None")

        self.versioned_document_uuid = document.uuid
        self.versioned_document_version = document.version

    def del_versioned_document(self):
        self.versioned_document_uuid = None
        self.versioned_document_version = None

    @hybrid_method
    def is_versioned_document(self, document):
        return (self.versioned_document_uuid == document.uuid
                and self.versioned_document_version == document.version)
Esempio n. 2
0
class LatestDocument:

    latest_document_uuid = UUID()
    latest_document = Function(fget='get_latest_document',
                               fset='set_latest_document',
                               fdel='del_latest_document')

    def get_latest_document(self):
        Document = self.registry.Attachment.Document.Latest
        query = Document.query()
        query = query.filter(Document.uuid == self.latest_document_uuid)
        return query.one_or_none()

    def set_latest_document(self, document):
        if document.uuid is None:
            raise NoneValueException("Uuid value is None")

        if document.type != 'latest':
            raise NotLatestException(
                "You try to set a versioned document, this action is "
                "forbidden")

        self.latest_document_uuid = document.uuid

    def del_latest_document(self):
        self.latest_document_uuid = None

    @hybrid_method
    def is_latest_document(self, document):
        if document.type != 'latest':
            raise NotLatestException(
                "You try to compare the latest document with a versioned "
                "document, this action is forbidden")

        return self.latest_document_uuid == document.uuid
Esempio n. 3
0
class WeatherStation(Model.Iot.State):
    """Weather state from APRS-IS packet"""

    STATE_TYPE = "WEATHER_STATION"

    uuid: uuid4 = UUID(
        primary_key=True,
        default=uuid4,
        binary=False,
        foreign_key=Model.Iot.State.use("uuid").options(ondelete="CASCADE"),
    )
    sensor_date: DateTime = DateTime(
        label="Sensor timestamp",
        index=True,
    )
    station_code: str = String(label="Station code",
                               unique=False,
                               index=True,
                               nullable=False)

    wind_direction: D = Decimal(label="Wind direction")
    wind_speed: D = Decimal(label="Wind Speed (km/h ?)")
    wind_gust: D = Decimal(label="Wind gust (km/h ?)")
    temperature: D = Decimal(label="Thermometer (°C)")
    rain_1h: D = Decimal(label="rain (mm/1h)")
    rain_24h: D = Decimal(label="rain (mm/24h)")
    rain_since_midnight: D = Decimal(label="rain (mm/since midnight)")
    humidity: D = Decimal(label="Humidity (%)")
    pressure: D = Decimal(label="Pressure (hPa)")
    luminosity: D = Decimal(label="Luminosity/irradiation (W/m2)")

    @classmethod
    def define_table_args(cls):
        res = super().define_table_args()
        return res + (UniqueConstraint(cls.sensor_date, cls.station_code), )

    @classmethod
    def get_device_state(
        cls,
        code: str,
    ) -> Union["registry.Model.Iot.State.Relay",
               "registry.Model.Iot.State.DesiredRelay",
               "registry.Model.Iot.State.Thermometer",
               "registry.Model.Iot.State.FuelGauge",
               "registry.Model.Iot.State.WeatherStation", ]:
        """Overwrite parent method to sort on sensor date and set fake date
        while creating empty state
        """
        Device = cls.registry.Iot.Device
        state = (cls.query().join(Device).filter(Device.code == code).order_by(
            cls.registry.Iot.State.WeatherStation.sensor_date.desc()).first())
        if not state:
            device = Device.query().filter_by(code=code).one()
            # We don't want to instert a new state here, just creating
            # a default instance
            state = cls(device=device, sensor_date=datetime.now())
            cls.registry.flush()
        return state
Esempio n. 4
0
class FuelGauge(Model.Iot.State):

    STATE_TYPE = "FUEL_GAUGE"

    uuid: uuid4 = UUID(
        primary_key=True,
        default=uuid4,
        binary=False,
        foreign_key=Model.Iot.State.use("uuid").options(ondelete="CASCADE"),
    )
    level: int = Integer(label="Fuel level (mm)")
Esempio n. 5
0
class MyTemplate(Declarations.Model.Attachment.Template):
    TYPE = 'MyTemplate'

    uuid = UUID(primary_key=True,
                nullable=False,
                binary=False,
                foreign_key=Declarations.Model.Attachment.Template.use('uuid'))
    other_option = String()

    def render(self, data):
        return self.__class__.file_
Esempio n. 6
0
class Relay(Model.Iot.State):

    STATE_TYPE = "RELAY"

    uuid: uuid4 = UUID(
        primary_key=True,
        default=uuid4,
        binary=False,
        foreign_key=Model.Iot.State.use("uuid").options(ondelete="CASCADE"),
    )
    is_open: bool = Boolean(label="Is open ?", default=True)
    """Current circuit state. is_open == True means circuit open, it's turned
Esempio n. 7
0
class Format(Attachment.Template):
    """Simple python format templating"""
    TYPE = TYPE

    uuid = UUID(
        primary_key=True, nullable=False, binary=False,
        foreign_key=Attachment.Template.use('uuid').options(ondelete='cascade'))
    contenttype = String(nullable=False)

    def render(self, data):
        template = self.get_template()
        return bytes(template.format(**data), encoding='utf-8')

    def update_document(self, document, file_, data):
        super(Format, self).update_document(document, file_, data)
        document.contenttype = self.contenttype
class Thing():
    uuid = UUID(primary_key=True, default=uuid1, binary=False)
    name = String(label="Name", unique=True, nullable=False)
    create_date = DateTime(default=datetime.now, nullable=False)
    edit_date = DateTime(default=datetime.now, nullable=False)
    secret = Password(crypt_context={'schemes': ['pbkdf2_sha512']},
                      nullable=False)
    example = Many2One(label="Example",
                       model=Model.Example,
                       one2many="things",
                       nullable=False)

    def __str__(self):
        return ('{self.name}').format(self=self)

    def __repr__(self):
        msg = ('<Thing: {self.name}, {self.uuid}>')
        return msg.format(self=self)
Esempio n. 9
0
class Message(Declarations.Mixin.DramatiqMessageStatus):
    """Message model for dramatiq"""

    id = UUID(primary_key=True, nullable=False)
    updated_at = DateTime()
    message = Json(nullable=False)

    def __str__(self):
        return '<Message (id={self.id}, status={self.status.label})>'.format(
            self=self)

    @classmethod
    def insert(cls, *args, **kwargs):
        """Over write the insert to add the first history line"""
        self = super(Message, cls).insert(*args, **kwargs)
        self.updated_at = datetime.now()
        self.registry.Dramatiq.Message.History.insert(
            status=self.status, created_at=self.updated_at, message=self)
        return self

    @classmethod
    def get_instance_of(cls, message):
        """Called by the middleware to get the model instance of the message"""
        return cls.query().filter(cls.id == message.message_id).one_or_none()

    def update_status(self, status, error=None):
        """Called by the middleware to change the status and history"""
        logger.info("Update message %s status => %s", self,
                    dict(self.STATUSES).get(status))
        self.status = status
        self.updated_at = datetime.now()
        self.registry.Dramatiq.Message.History.insert(
            status=status,
            created_at=self.updated_at,
            message=self,
            error=error)
Esempio n. 10
0
class Thermometer(Model.Iot.State):

    STATE_TYPE = "TEMPERATURE"

    uuid: uuid4 = UUID(
        primary_key=True,
        default=uuid4,
        binary=False,
        foreign_key=Model.Iot.State.use("uuid").options(ondelete="CASCADE"),
    )

    celsius: D = Decimal(label="Thermometer (°C)")

    @classmethod
    def get_last_value(cls, device_code: str):
        Device = cls.registry.Iot.Device
        last = (cls.query().join(Device).filter(
            Device.code == device_code).order_by(
                cls.registry.Iot.State.create_date.desc()).first())
        if last:
            return last.celsius
        return 0

    @classmethod
    def wait_min_return_desired_temperature(cls, device_sensor: DEVICE,
                                            device_min: DEVICE,
                                            device_max: DEVICE):
        # wait return temperature to get the DEVICE.MIN_RET_DESIRED.value
        # temperature before start burner again
        Device = cls.registry.Iot.Device
        State = cls.registry.Iot.State
        min_temp = cls.get_last_value(device_min.value)
        max_temp = cls.get_last_value(device_max.value)
        last_pick = (cls.query().join(Device).filter(
            Device.code == device_sensor.value).filter(
                cls.celsius >= max_temp).order_by(
                    State.create_date.desc()).first())
        if not last_pick:
            return False
        last_curve = (cls.query().join(Device).filter(
            Device.code == device_sensor.value).filter(
                cls.celsius <= min_temp).filter(
                    State.create_date > last_pick.create_date).order_by(
                        State.create_date.desc()).first())
        if not last_curve:
            return True
        return False

    @classmethod
    def get_living_room_avg(cls, date: datetime = None, minutes=5):
        avg = cls.get_sensor_avg(DEVICE.LIVING_ROOM_SENSOR.value,
                                 date=date,
                                 minutes=minutes)
        if not avg:
            return 0
        return avg

    @classmethod
    def get_sensor_avg(cls,
                       device_code: str,
                       date: datetime = None,
                       minutes: int = 15):
        if not date:
            date = datetime.now()
        Device = cls.registry.Iot.Device
        return (cls.query(func.avg(
            cls.celsius).label("average")).join(Device).filter(
                Device.code == device_code).filter(
                    cls.create_date > date - timedelta(minutes=minutes),
                    cls.create_date <= date,
                ).group_by(Device.code).scalar())
Esempio n. 11
0
class DesiredRelay(Model.Iot.State):

    STATE_TYPE = "DESIRED_RELAY"

    uuid: uuid4 = UUID(
        primary_key=True,
        default=uuid4,
        binary=False,
        foreign_key=Model.Iot.State.use("uuid").options(ondelete="CASCADE"),
    )
    is_open: bool = Boolean(label="Is open ?", default=True)
    """Current circuit state. is_open == True means circuit open, it's turned
    off"""
    @classmethod
    def get_device_state(cls,
                         code: str,
                         date: datetime = None) -> "DesiredRelay":
        """return desired state for given device code"""
        mode = ThermostatMode(
            cls.registry.System.Parameter.get("mode", default="manual"))
        if mode is ThermostatMode.manual:
            return super().get_device_state(code)
        else:
            if not date:
                date = datetime.now()
            return {
                "BURNER": cls.get_burner_thermostat_desired_state,
                "ENGINE": cls.get_engine_thermostat_desired_state,
            }[code](date)

    @classmethod
    def get_burner_thermostat_desired_state(cls,
                                            date: datetime) -> "DesiredRelay":
        Thermometer = cls.registry.Iot.State.Thermometer
        Range = cls.registry.Iot.Thermostat.Range
        celsius_avg = Thermometer.get_living_room_avg(date=date, minutes=5)
        if not celsius_avg:
            # there are no thermometer working
            # user should move to manual mode
            return cls(is_open=True)
        if Thermometer.wait_min_return_desired_temperature(
                DEVICE.DEPARTURE_SENSOR,
                DEVICE.MIN_RET_DESIRED,
                DEVICE.MAX_DEP_DESIRED,
        ):
            # departure is to hot or waiting under ret min desired
            return cls(is_open=True)
        celsius_desired = Range.get_desired_living_room_temperature(date)
        if celsius_avg >= celsius_desired:
            # living room is already warm
            return cls(is_open=True)
        if Thermometer.get_last_value(
                DEVICE.RETURN_SENOR.value) >= Thermometer.get_last_value(
                    DEVICE.MAX_RET_DESIRED.value):
            # return temperature is to hot
            return cls(is_open=True)
        if Thermometer.wait_min_return_desired_temperature(
                DEVICE.RETURN_SENOR,
                DEVICE.MIN_RET_DESIRED,
                DEVICE.MAX_RET_DESIRED,
        ):
            # wait return temperature to get the DEVICE.MIN_RET_DESIRED.value
            # temperature before start burner again
            return cls(is_open=True)
        return cls(is_open=False)

    @classmethod
    def get_engine_thermostat_desired_state(cls,
                                            date: datetime,
                                            delta_minutes=120
                                            ) -> "DesiredRelay":
        """
        If 
          burner relay was on in last delta_minutes,
          or if return water - living room is more than diff config value
        then engine must be turned on
        """
        Thermometer = cls.registry.Iot.State.Thermometer
        Relay = cls.registry.Iot.State.Relay
        Device = cls.registry.Iot.Device
        count_states = (Relay.query().join(Device).filter(
            Relay.create_date > date - timedelta(minutes=delta_minutes),
            Relay.create_date <= date,
            Device.code == "BURNER",
            Relay.is_open == False,
        ))
        living_room_avg_temp = Thermometer.get_living_room_avg(date=date,
                                                               minutes=5)
        return_temp = Thermometer.get_last_value(DEVICE.RETURN_SENOR.value)
        min_diff = Thermometer.get_last_value(DEVICE.MIN_DIFF_DESIRED.value)
        return cls(
            is_open=not ((count_states.count() > 0) or
                         (return_temp - living_room_avg_temp > min_diff)))
Esempio n. 12
0
class Document:
    DOCUMENT_TYPE = None

    uuid = UUID(primary_key=True, binary=False, nullable=False)
    version_number = Integer(primary_key=True, nullable=False)
    version = Function(fget="get_version")

    created_at = DateTime(default=datetime.now, nullable=False)
    historied_at = DateTime()

    data = Json(default=dict)

    file_added_at = DateTime()
    filename = String(size=256)
    contenttype = String()
    filesize = Integer()
    file = LargeBinary()
    hash = String(size=256)

    type = Selection(selections={
        'latest': 'Latest',
        'history': 'History'
    },
                     nullable=False)

    previous_doc_uuid = UUID()
    previous_doc_version_number = Integer()
    previous_version = Function(fget="get_previous_version")
    next_version = Function(fget="get_next_version")
    previous_versions = Function(fget="get_previous_versions")

    @classmethod
    def get_file_fields(cls):
        return [
            'file', 'file_added_at', 'contenttype', 'filename', 'hash',
            'filesize'
        ]

    def get_file(self):
        return self.to_dict(*self.get_file_fields())

    def set_file(self, file_):
        self.file = file_

    def get_version(self):
        return "V-%06d" % self.version_number

    def get_previous_version(self):
        Doc = self.anyblok.Attachment.Document
        query = Doc.query()
        query = query.filter(Doc.uuid == self.previous_doc_uuid)
        query = query.filter(
            Doc.version_number == self.previous_doc_version_number)
        return query.one_or_none()

    def get_next_version(self):
        Doc = self.anyblok.Attachment.Document
        query = Doc.query()
        query = query.filter(Doc.previous_doc_uuid == self.uuid)
        query = query.filter(
            Doc.previous_doc_version_number == self.version_number)
        return query.one_or_none()

    def get_previous_versions(self):
        res = []
        current = self
        while current.previous_version:
            current = current.previous_version
            res.append(current)

        return res

    @classmethod
    def define_mapper_args(cls):
        mapper_args = super(Document, cls).define_mapper_args()
        if cls.__registry_name__ == 'Model.Attachment.Document':
            mapper_args.update({'polymorphic_on': cls.type})

        mapper_args.update({'polymorphic_identity': cls.DOCUMENT_TYPE})
        return mapper_args

    @classmethod
    def insert(cls, *args, **kwargs):
        if cls.__registry_name__ == 'Model.Attachment.Document':
            return cls.anyblok.Attachment.Document.Latest.insert(
                *args, **kwargs)

        return super(Document, cls).insert(*args, **kwargs)

    @classmethod
    def query(cls, *args, **kwargs):
        query = super(Document, cls).query(*args, **kwargs)
        if cls.__registry_name__ != 'Model.Attachment.Document':
            query = query.filter(cls.type == cls.DOCUMENT_TYPE)

        return query

    def has_file(self):
        if self.file:
            return True

        return False

    @classmethod
    def filter_has_not_file(cls):
        return cls.file == None  # noqa
Esempio n. 13
0
class Job:
    """The job is an execution of an instance of task"""
    STATUS_NEW = "new"
    STATUS_WAITING = "waiting"
    STATUS_RUNNING = "running"
    STATUS_FAILED = "failed"
    STATUS_DONE = "done"

    STATUSES = [
        (STATUS_NEW, "New"),
        (STATUS_WAITING, "Waiting"),
        (STATUS_RUNNING, "Running"),
        (STATUS_FAILED, "Failed"),
        (STATUS_DONE, "Done"),
    ]

    uuid = UUID(primary_key=True, nullable=False, default=uuid1, binary=False)
    create_at = DateTime(default=datetime.now, nullable=False)
    update_at = DateTime(default=datetime.now,
                         nullable=False,
                         auto_update=True)
    run_at = DateTime()
    data = Json(nullable=False)
    status = Selection(selections=STATUSES, default=STATUS_NEW, nullable=False)
    task = Many2One(model=Declarations.Model.Dramatiq.Task, nullable=False)
    main_job = Many2One(model='Model.Dramatiq.Job', one2many="sub_jobs")
    error = Text()

    @actor_send()
    def run(cls, job_uuid=None):
        """dramatiq actor to execute a specific task"""
        autocommit = EnvironmentManager.get('job_autocommit', True)
        try:
            job = cls.query().filter(cls.uuid == job_uuid).one()
            job.status = cls.STATUS_RUNNING
            if autocommit:
                cls.registry.commit()  # use to save the state

            job.task.run(job)

            if autocommit:
                cls.registry.commit()  # use to save the state
        except Exception as e:
            logger.error(str(e))
            cls.registry.rollback()
            job.status = cls.STATUS_FAILED
            job.error = str(e)

            if autocommit:
                cls.registry.commit()  # use to save the state

            raise e

    def lock(self):
        """lock the job to be sure that only one thread execute the run_next"""
        Job = self.__class__
        while True:
            try:
                Job.query().with_for_update(nowait=True).filter(
                    Job.uuid == self.uuid).one()
                break
            except OperationalError:
                sleep(1)

    def call_main_job(self):
        """Call the main job if exist to do the next action of the main job"""
        if self.main_job:
            self.main_job.lock()
            self.main_job.task.run_next(self.main_job)
Esempio n. 14
0
class Shipment(Mixin.UuidColumn, Mixin.TrackModel):
    """ Shipment
    """
    statuses = dict(new="New",
                    label="Label",
                    transit="Transit",
                    delivered="Delivered",
                    exception="Exception",
                    error="Error")
    service = Many2One(label="Shipping service",
                       model=Declarations.Model.Delivery.Carrier.Service,
                       one2many='shipments',
                       nullable=False)
    sender_address = Many2One(label="Sender address",
                              model=Declarations.Model.Address,
                              column_names=["sender_address_uuid"],
                              nullable=False)
    recipient_address = Many2One(label="Recipient address",
                                 model=Declarations.Model.Address,
                                 column_names=["recipient_address_uuid"],
                                 nullable=False)
    reason = String(label="Reason reference")
    pack = String(label="Pack reference")
    status = Selection(label="Shipping status",
                       selections=statuses,
                       default='new',
                       nullable=False)
    properties = Jsonb(label="Properties")
    document_uuid = UUID(label="Carrier slip document reference")
    document = Function(fget='get_latest_document')
    cn23_document_uuid = UUID(label="Carrier slip document reference")
    cn23_document = Function(fget='get_latest_cn23_document')
    tracking_number = String(label="Carrier tracking number")

    def _get_latest_cocument(self, document_uuid):
        Document = self.registry.Attachment.Document.Latest
        query = Document.query().filter_by(uuid=document_uuid)
        return query.one_or_none()

    def get_latest_document(self):
        return self._get_latest_cocument(self.document_uuid)

    def get_latest_cn23_document(self):
        return self._get_latest_cocument(self.cn23_document_uuid)

    def create_label(self):
        """Retrieve a shipping label from shipping service
        """
        if not self.status == 'new':
            return

        return self.service.create_label(shipment=self)

    def get_label_status(self):
        """Retrieve a shipping label from shipping service
        """
        if self.status in ('new', 'delivered', 'error'):
            return

        return self.service.get_label_status(shipment=self)

    @classmethod
    def get_labels_status(cls):
        status = ['label', 'transit', 'exception']
        shipments = cls.query().filter(cls.status.in_(status)).all()
        for shipment in shipments:
            shipment.get_label_status()

    def _save_document(self, document, binary_file, content_type):
        document.set_file(binary_file)
        document.filesize = len(binary_file)
        document.contenttype = content_type
        hash = hashlib.sha256()
        hash.update(binary_file)
        document.hash = hash.digest()

        self.registry.flush()  # flush to update version in document

    def save_document(self, binary_file, content_type):
        document = self.document
        if document is None:
            document = self.registry.Attachment.Document.insert(
                data={'shipment': str(self.uuid)})
            self.document_uuid = document.uuid

        self._save_document(document, binary_file, content_type)

    def save_cn23_document(self, binary_file, content_type):
        document = self.cn23_document
        if document is None:
            document = self.registry.Attachment.Document.insert(
                data={'shipment': str(self.uuid)})
            self.cn23_document_uuid = document.uuid

        self._save_document(document, binary_file, content_type)
Esempio n. 15
0
class UuidColumn:
    """ `UUID` id primary key mixin
    """
    uuid = UUID(primary_key=True, default=uuid4, binary=False)
class Jinja(Mixin.WkHtml2Pdf, Attachment.Template):
    """Jinja templating"""
    TYPE = TYPE

    uuid = UUID(primary_key=True,
                nullable=False,
                binary=False,
                foreign_key=Attachment.Template.use('uuid').options(
                    ondelete='cascade'))
    jinja_paths = Text(nullable=False)
    contenttype = Selection(selections={
        'text/html': 'HTML',
        'application/pdf': 'PDF',
    },
                            default='application/pdf',
                            nullable=False)
    options = Json(default={}, nullable=False)
    wkhtml2pdf_configuration = Many2One(
        model=Declarations.Model.Attachment.WkHtml2Pdf, nullable=True)

    def check_flush_validity(self):
        super(Jinja, self).check_flush_validity()
        if self.contenttype == 'application/pdf':
            if not self.wkhtml2pdf_configuration:
                raise TemplateJinjaException(
                    "No WkHtml2Pdf configuration for %r" % self)

    def update_document(self, document, file_, data):
        super(Jinja, self).update_document(document, file_, data)
        document.contenttype = self.contenttype

    def render(self, data):
        if self.contenttype == 'text/html':
            return self.render_html(data)

        return self.render_pdf(data)

    def render_html(self, data):
        jinja_paths = []
        if self.jinja_paths:
            for jinja_path in self.jinja_paths.split(','):
                jinja_path = format_path(jinja_path.strip())
                if not os.path.isdir(jinja_path):
                    raise TemplateJinjaException("%r must be a folder" %
                                                 jinja_path)

                jinja_paths.append(jinja_path)

        jinja_env = SandboxedEnvironment(
            loader=jinja2.FileSystemLoader(jinja_paths),
            undefined=jinja2.StrictUndefined,
        )
        parser = self.get_parser()
        if not hasattr(parser, 'serialize_jinja_options'):
            raise TemplateJinjaException(
                ("The parser %r must declare a method "
                 "'serialize_jinja_options' for %r") % (parser, self))

        options = self.get_parser().serialize_jinja_options(self.options)
        return jinja_env.from_string(self.get_template()).render(
            data=data, str=str, **options).encode('utf-8')

    def render_pdf(self, data):
        html_content = self.render_html(data)
        return self.wkhtml2pdf(html_content)