Merge branch 'master' into rate
# Conflicts: # ereuse_devicehub/resources/event/models.py # ereuse_devicehub/resources/event/models.pyi # ereuse_devicehub/resources/event/schemas.py
This commit is contained in:
commit
e6d496872c
|
@ -27,3 +27,45 @@ To remove children lots the idea is the same:
|
||||||
And for devices is all the same:
|
And for devices is all the same:
|
||||||
``POST /lots/<parent-lot-id>/devices/?id=<device-id-1>&id=<device-id-2>``;
|
``POST /lots/<parent-lot-id>/devices/?id=<device-id-1>&id=<device-id-2>``;
|
||||||
idem for removing devices.
|
idem for removing devices.
|
||||||
|
|
||||||
|
|
||||||
|
Sharing lots
|
||||||
|
************
|
||||||
|
Sharing a lot means giving certain permissions to users, like reading
|
||||||
|
the characteristics of devices, or performing certain events.
|
||||||
|
|
||||||
|
Linking lots
|
||||||
|
============
|
||||||
|
Linking lots means setting a reference to a lot in another Devicehub
|
||||||
|
which supposedly have the same devices. One of both lots is considered
|
||||||
|
to be the outgoing lot and the other one the ingoing, in the sense
|
||||||
|
that the lifetime of the devices of the outgoing lot precedes the
|
||||||
|
incoming lot; in other way, the events of the devices in the outgoing
|
||||||
|
lot should (but not enforced) happen before that the events in the
|
||||||
|
incoming lot.
|
||||||
|
|
||||||
|
The lot has two fields that uses to keep a link: the Devicehub URL and
|
||||||
|
the ID of the other lot (internal ID I suppose...).
|
||||||
|
|
||||||
|
A device can only be in one incoming lot and in one outgoing lot.
|
||||||
|
|
||||||
|
A device inside a linked lot, when it is confirmed the existence of
|
||||||
|
an equal device in the other DB, is considered to be linked.
|
||||||
|
|
||||||
|
A linked lot defines a both way share of READ Characteristics, at
|
||||||
|
least.
|
||||||
|
|
||||||
|
Users can sync the characteristics of devices + selected tags + selected rules,
|
||||||
|
copying (but not deleting) devices between them. This ensures no
|
||||||
|
traceability warnings when generating the lifetime of devices. In first
|
||||||
|
iterations this process could be done manually (to allow user select
|
||||||
|
tags and rules)
|
||||||
|
|
||||||
|
|
||||||
|
Why linking lots
|
||||||
|
----------------
|
||||||
|
|
||||||
|
* Get the lifetime of devices (reporting). This way a Devicehub
|
||||||
|
traverses the linked devices (as long as it has perm).
|
||||||
|
* Send devices to another Devicehub (characteristics).
|
||||||
|
* Ensure rules over linked devices.
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import csv
|
import csv
|
||||||
import pathlib
|
import pathlib
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
from fractions import Fraction
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
from typing import Dict, List, Set
|
from typing import Dict, List, Set
|
||||||
|
@ -12,6 +13,7 @@ from more_itertools import unique_everseen
|
||||||
from sqlalchemy import BigInteger, Boolean, Column, Enum as DBEnum, Float, ForeignKey, Integer, \
|
from sqlalchemy import BigInteger, Boolean, Column, Enum as DBEnum, Float, ForeignKey, Integer, \
|
||||||
Sequence, SmallInteger, Unicode, inspect, text
|
Sequence, SmallInteger, Unicode, inspect, text
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
|
from sqlalchemy.ext.hybrid import hybrid_property
|
||||||
from sqlalchemy.orm import ColumnProperty, backref, relationship, validates
|
from sqlalchemy.orm import ColumnProperty, backref, relationship, validates
|
||||||
from sqlalchemy.util import OrderedSet
|
from sqlalchemy.util import OrderedSet
|
||||||
from sqlalchemy_utils import ColorType
|
from sqlalchemy_utils import ColorType
|
||||||
|
@ -23,8 +25,8 @@ from teal.marshmallow import ValidationError
|
||||||
from teal.resource import url_for_resource
|
from teal.resource import url_for_resource
|
||||||
|
|
||||||
from ereuse_devicehub.db import db
|
from ereuse_devicehub.db import db
|
||||||
from ereuse_devicehub.resources.enums import ComputerChassis, DataStorageInterface, DisplayTech, \
|
from ereuse_devicehub.resources.enums import BatteryTechnology, ComputerChassis, \
|
||||||
PrinterTechnology, RamFormat, RamInterface, Severity
|
DataStorageInterface, DisplayTech, PrinterTechnology, RamFormat, RamInterface, Severity
|
||||||
from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing
|
from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing
|
||||||
|
|
||||||
|
|
||||||
|
@ -58,14 +60,15 @@ class Device(Thing):
|
||||||
so it can re-generated *offline*.
|
so it can re-generated *offline*.
|
||||||
|
|
||||||
""" + HID_CONVERSION_DOC
|
""" + HID_CONVERSION_DOC
|
||||||
model = Column(Unicode(), check_lower('model'))
|
model = Column(Unicode, check_lower('model'))
|
||||||
model.comment = """The model or brand of the device in lower case.
|
model.comment = """The model of the device in lower case.
|
||||||
|
|
||||||
Devices usually report one of both (model or brand). This value
|
The model is the unambiguous, as technical as possible, denomination
|
||||||
must be consistent through time.
|
for the product. This field, among others, is used to identify
|
||||||
|
the product.
|
||||||
"""
|
"""
|
||||||
manufacturer = Column(Unicode(), check_lower('manufacturer'))
|
manufacturer = Column(Unicode(), check_lower('manufacturer'))
|
||||||
manufacturer.comment = """The normalized name of the manufacturer
|
manufacturer.comment = """The normalized name of the manufacturer,
|
||||||
in lower case.
|
in lower case.
|
||||||
|
|
||||||
Although as of now Devicehub does not enforce normalization,
|
Although as of now Devicehub does not enforce normalization,
|
||||||
|
@ -74,26 +77,38 @@ class Device(Thing):
|
||||||
"""
|
"""
|
||||||
serial_number = Column(Unicode(), check_lower('serial_number'))
|
serial_number = Column(Unicode(), check_lower('serial_number'))
|
||||||
serial_number.comment = """The serial number of the device in lower case."""
|
serial_number.comment = """The serial number of the device in lower case."""
|
||||||
|
brand = db.Column(CIText())
|
||||||
|
brand.comment = """A naming for consumers. This field can represent
|
||||||
|
several models, so it can be ambiguous, and it is not used to
|
||||||
|
identify the product.
|
||||||
|
"""
|
||||||
|
generation = db.Column(db.SmallInteger, check_range('generation', 0))
|
||||||
|
generation.comment = """The generation of the device."""
|
||||||
weight = Column(Float(decimal_return_scale=3), check_range('weight', 0.1, 5))
|
weight = Column(Float(decimal_return_scale=3), check_range('weight', 0.1, 5))
|
||||||
weight.comment = """
|
weight.comment = """
|
||||||
The weight of the device.
|
The weight of the device.
|
||||||
"""
|
"""
|
||||||
width = Column(Float(decimal_return_scale=3), check_range('width', 0.1, 5))
|
width = Column(Float(decimal_return_scale=3), check_range('width', 0.1, 5))
|
||||||
width.comment = """
|
width.comment = """
|
||||||
The width of the device.
|
The width of the device in meters.
|
||||||
"""
|
"""
|
||||||
height = Column(Float(decimal_return_scale=3), check_range('height', 0.1, 5))
|
height = Column(Float(decimal_return_scale=3), check_range('height', 0.1, 5))
|
||||||
height.comment = """
|
height.comment = """
|
||||||
The height of the device.
|
The height of the device in meters.
|
||||||
"""
|
"""
|
||||||
depth = Column(Float(decimal_return_scale=3), check_range('depth', 0.1, 5))
|
depth = Column(Float(decimal_return_scale=3), check_range('depth', 0.1, 5))
|
||||||
depth.comment = """
|
depth.comment = """
|
||||||
The depth of the device.
|
The depth of the device in meters.
|
||||||
"""
|
"""
|
||||||
color = Column(ColorType)
|
color = Column(ColorType)
|
||||||
color.comment = """The predominant color of the device."""
|
color.comment = """The predominant color of the device."""
|
||||||
production_date = Column(db.TIMESTAMP(timezone=True))
|
production_date = Column(db.DateTime)
|
||||||
production_date.comment = """The date of production of the device."""
|
production_date.comment = """The date of production of the device.
|
||||||
|
This is timezone naive, as Workbench cannot report this data
|
||||||
|
with timezone information.
|
||||||
|
"""
|
||||||
|
variant = Column(Unicode)
|
||||||
|
variant.comment = """A variant or sub-model of the device."""
|
||||||
|
|
||||||
_NON_PHYSICAL_PROPS = {
|
_NON_PHYSICAL_PROPS = {
|
||||||
'id',
|
'id',
|
||||||
|
@ -107,7 +122,11 @@ class Device(Thing):
|
||||||
'width',
|
'width',
|
||||||
'height',
|
'height',
|
||||||
'depth',
|
'depth',
|
||||||
'weight'
|
'weight',
|
||||||
|
'brand',
|
||||||
|
'generation',
|
||||||
|
'production_date',
|
||||||
|
'variant'
|
||||||
}
|
}
|
||||||
|
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
|
@ -129,7 +148,7 @@ class Device(Thing):
|
||||||
2. Events performed to a component.
|
2. Events performed to a component.
|
||||||
3. Events performed to a parent device.
|
3. Events performed to a parent device.
|
||||||
|
|
||||||
Events are returned by ascending ``created`` time.
|
Events are returned by descending ``created`` time.
|
||||||
"""
|
"""
|
||||||
return sorted(chain(self.events_multiple, self.events_one), key=self.EVENT_SORT_KEY)
|
return sorted(chain(self.events_multiple, self.events_one), key=self.EVENT_SORT_KEY)
|
||||||
|
|
||||||
|
@ -260,6 +279,7 @@ class Device(Thing):
|
||||||
:raise LookupError: Device has not an event of the given type.
|
:raise LookupError: Device has not an event of the given type.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
# noinspection PyTypeHints
|
||||||
return next(e for e in reversed(self.events) if isinstance(e, types))
|
return next(e for e in reversed(self.events) if isinstance(e, types))
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
raise LookupError('{!r} does not contain events of types {}.'.format(self, types))
|
raise LookupError('{!r} does not contain events of types {}.'.format(self, types))
|
||||||
|
@ -288,12 +308,8 @@ class Device(Thing):
|
||||||
|
|
||||||
|
|
||||||
class DisplayMixin:
|
class DisplayMixin:
|
||||||
"""
|
"""Base class for the Display Component and the Monitor Device."""
|
||||||
Aspect ratio can be computed as in
|
size = Column(Float(decimal_return_scale=1), check_range('size', 2, 150), nullable=False)
|
||||||
https://github.com/mirukan/whratio/blob/master/whratio/ratio.py and
|
|
||||||
could be a future property.
|
|
||||||
"""
|
|
||||||
size = Column(Float(decimal_return_scale=2), check_range('size', 2, 150))
|
|
||||||
size.comment = """
|
size.comment = """
|
||||||
The size of the monitor in inches.
|
The size of the monitor in inches.
|
||||||
"""
|
"""
|
||||||
|
@ -301,27 +317,59 @@ class DisplayMixin:
|
||||||
technology.comment = """
|
technology.comment = """
|
||||||
The technology the monitor uses to display the image.
|
The technology the monitor uses to display the image.
|
||||||
"""
|
"""
|
||||||
resolution_width = Column(SmallInteger, check_range('resolution_width', 10, 20000))
|
resolution_width = Column(SmallInteger, check_range('resolution_width', 10, 20000),
|
||||||
|
nullable=False)
|
||||||
resolution_width.comment = """
|
resolution_width.comment = """
|
||||||
The maximum horizontal resolution the monitor can natively support
|
The maximum horizontal resolution the monitor can natively support
|
||||||
in pixels.
|
in pixels.
|
||||||
"""
|
"""
|
||||||
resolution_height = Column(SmallInteger, check_range('resolution_height', 10, 20000))
|
resolution_height = Column(SmallInteger, check_range('resolution_height', 10, 20000),
|
||||||
|
nullable=False)
|
||||||
resolution_height.comment = """
|
resolution_height.comment = """
|
||||||
The maximum vertical resolution the monitor can natively support
|
The maximum vertical resolution the monitor can natively support
|
||||||
in pixels.
|
in pixels.
|
||||||
"""
|
"""
|
||||||
refresh_rate = Column(SmallInteger, check_range('refresh_rate', 10, 1000))
|
refresh_rate = Column(SmallInteger, check_range('refresh_rate', 10, 1000))
|
||||||
contrast_ratio = Column(SmallInteger, check_range('contrast_ratio', 100, 100000))
|
contrast_ratio = Column(SmallInteger, check_range('contrast_ratio', 100, 100000))
|
||||||
touchable = Column(Boolean, nullable=False, default=False)
|
touchable = Column(Boolean)
|
||||||
touchable.comment = """Whether it is a touchscreen."""
|
touchable.comment = """Whether it is a touchscreen."""
|
||||||
|
|
||||||
|
@hybrid_property
|
||||||
|
def aspect_ratio(self):
|
||||||
|
"""The aspect ratio of the display, as a fraction: ``X/Y``.
|
||||||
|
|
||||||
|
Regular values are ``4/3``, ``5/4``, ``16/9``, ``21/9``,
|
||||||
|
``14/10``, ``19/10``, ``16/10``.
|
||||||
|
"""
|
||||||
|
return Fraction(self.resolution_width, self.resolution_height)
|
||||||
|
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
@aspect_ratio.expression
|
||||||
|
def aspect_ratio(cls):
|
||||||
|
# The aspect ratio to use as SQL in the DB
|
||||||
|
# This allows comparing resolutions
|
||||||
|
return db.func.round(cls.resolution_width / cls.resolution_height, 2)
|
||||||
|
|
||||||
|
@hybrid_property
|
||||||
|
def widescreen(self):
|
||||||
|
"""Whether the monitor is considered to be widescreen.
|
||||||
|
|
||||||
|
Widescreen monitors are those having a higher aspect ratio
|
||||||
|
greater than 4/3.
|
||||||
|
"""
|
||||||
|
# We add a tiny extra to 4/3 to avoid precision errors
|
||||||
|
return self.aspect_ratio > 4.001 / 3
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return '{0.t} {0.serial_number} {0.size}in ({0.aspect_ratio}) {0.technology}'.format(self)
|
||||||
|
|
||||||
def __format__(self, format_spec: str) -> str:
|
def __format__(self, format_spec: str) -> str:
|
||||||
v = ''
|
v = ''
|
||||||
if 't' in format_spec:
|
if 't' in format_spec:
|
||||||
v += '{0.t} {0.model}'.format(self)
|
v += '{0.t} {0.model}'.format(self)
|
||||||
if 's' in format_spec:
|
if 's' in format_spec:
|
||||||
v += '({0.manufacturer}) S/N {0.serial_number} – {0.size}in {0.technology}'
|
v += '({0.manufacturer}) S/N {0.serial_number}'.format(self)
|
||||||
|
v += '– {0.size}in ({0.aspect_ratio}) {0.technology}'.format(self)
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
@ -444,14 +492,16 @@ class Mobile(Device):
|
||||||
|
|
||||||
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||||
imei = Column(BigInteger)
|
imei = Column(BigInteger)
|
||||||
imei.comment = """
|
imei.comment = """The International Mobile Equipment Identity of
|
||||||
The International Mobile Equipment Identity of the smartphone
|
the smartphone as an integer.
|
||||||
as an integer.
|
|
||||||
"""
|
"""
|
||||||
meid = Column(Unicode)
|
meid = Column(Unicode)
|
||||||
meid.comment = """
|
meid.comment = """The Mobile Equipment Identifier as a hexadecimal
|
||||||
The Mobile Equipment Identifier as a hexadecimal string.
|
string.
|
||||||
"""
|
"""
|
||||||
|
ram_size = db.Column(db.Integer, check_range(1, ))
|
||||||
|
ram_size.comment = """The total of RAM of the device in MB."""
|
||||||
|
data_storage_size = db.Column(db.Integer)
|
||||||
|
|
||||||
@validates('imei')
|
@validates('imei')
|
||||||
def validate_imei(self, _, value: int):
|
def validate_imei(self, _, value: int):
|
||||||
|
@ -577,6 +627,8 @@ class Motherboard(JoinedComponentTableMixin, Component):
|
||||||
firewire = Column(SmallInteger, check_range('firewire', min=0))
|
firewire = Column(SmallInteger, check_range('firewire', min=0))
|
||||||
serial = Column(SmallInteger, check_range('serial', min=0))
|
serial = Column(SmallInteger, check_range('serial', min=0))
|
||||||
pcmcia = Column(SmallInteger, check_range('pcmcia', min=0))
|
pcmcia = Column(SmallInteger, check_range('pcmcia', min=0))
|
||||||
|
bios_date = Column(db.Date)
|
||||||
|
bios_date.comment = """The date of the BIOS version."""
|
||||||
|
|
||||||
|
|
||||||
class NetworkMixin:
|
class NetworkMixin:
|
||||||
|
@ -610,6 +662,8 @@ class Processor(JoinedComponentTableMixin, Component):
|
||||||
threads.comment = """The number of threads per core."""
|
threads.comment = """The number of threads per core."""
|
||||||
address = Column(SmallInteger, check_range('address', 8, 256))
|
address = Column(SmallInteger, check_range('address', 8, 256))
|
||||||
address.comment = """The address of the CPU: 8, 16, 32, 64, 128 or 256 bits."""
|
address.comment = """The address of the CPU: 8, 16, 32, 64, 128 or 256 bits."""
|
||||||
|
abi = Column(Unicode, check_lower('abi'))
|
||||||
|
abi.comment = """The Application Binary Interface of the processor."""
|
||||||
|
|
||||||
|
|
||||||
class RamModule(JoinedComponentTableMixin, Component):
|
class RamModule(JoinedComponentTableMixin, Component):
|
||||||
|
@ -635,6 +689,24 @@ class Display(JoinedComponentTableMixin, DisplayMixin, Component):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Battery(JoinedComponentTableMixin, Component):
|
||||||
|
wireless = db.Column(db.Boolean)
|
||||||
|
wireless.comment = """If the battery can be charged wirelessly."""
|
||||||
|
technology = db.Column(db.Enum(BatteryTechnology))
|
||||||
|
size = db.Column(db.Integer, nullable=False)
|
||||||
|
size.comment = """Maximum battery capacity by design, in mAh.
|
||||||
|
|
||||||
|
Use BatteryTest's "size" to get the actual size of the battery.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def capacity(self) -> float:
|
||||||
|
"""The quantity of """
|
||||||
|
from ereuse_devicehub.resources.event.models import MeasureBattery
|
||||||
|
real_size = self.last_event_of(MeasureBattery).size
|
||||||
|
return real_size / self.size if real_size and self.size else None
|
||||||
|
|
||||||
|
|
||||||
class ComputerAccessory(Device):
|
class ComputerAccessory(Device):
|
||||||
"""Computer peripherals and similar accessories."""
|
"""Computer peripherals and similar accessories."""
|
||||||
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from fractions import Fraction
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
from typing import Dict, Generator, Iterable, List, Optional, Set, Type
|
from typing import Dict, Generator, Iterable, List, Optional, Set, Type, TypeVar
|
||||||
|
|
||||||
from boltons import urlutils
|
from boltons import urlutils
|
||||||
from boltons.urlutils import URL
|
from boltons.urlutils import URL
|
||||||
|
@ -12,15 +13,16 @@ from teal.enums import Layouts
|
||||||
|
|
||||||
from ereuse_devicehub.resources.agent.models import Agent
|
from ereuse_devicehub.resources.agent.models import Agent
|
||||||
from ereuse_devicehub.resources.device import states
|
from ereuse_devicehub.resources.device import states
|
||||||
from ereuse_devicehub.resources.enums import ComputerChassis, DataStorageInterface, DisplayTech, \
|
from ereuse_devicehub.resources.enums import BatteryTechnology, ComputerChassis, \
|
||||||
PrinterTechnology, RamFormat, RamInterface
|
DataStorageInterface, DisplayTech, PrinterTechnology, RamFormat, RamInterface
|
||||||
from ereuse_devicehub.resources.event import models as e
|
from ereuse_devicehub.resources.event import models as e
|
||||||
from ereuse_devicehub.resources.image.models import ImageList
|
|
||||||
from ereuse_devicehub.resources.lot.models import Lot
|
from ereuse_devicehub.resources.lot.models import Lot
|
||||||
from ereuse_devicehub.resources.models import Thing
|
from ereuse_devicehub.resources.models import Thing
|
||||||
from ereuse_devicehub.resources.tag import Tag
|
from ereuse_devicehub.resources.tag import Tag
|
||||||
from ereuse_devicehub.resources.tag.model import Tags
|
from ereuse_devicehub.resources.tag.model import Tags
|
||||||
|
|
||||||
|
E = TypeVar('E', bound=e.Event)
|
||||||
|
|
||||||
|
|
||||||
class Device(Thing):
|
class Device(Thing):
|
||||||
EVENT_SORT_KEY = attrgetter('created')
|
EVENT_SORT_KEY = attrgetter('created')
|
||||||
|
@ -38,27 +40,32 @@ class Device(Thing):
|
||||||
color = ... # type: Column
|
color = ... # type: Column
|
||||||
lots = ... # type: relationship
|
lots = ... # type: relationship
|
||||||
production_date = ... # type: Column
|
production_date = ... # type: Column
|
||||||
|
brand = ... # type: Column
|
||||||
|
generation = ... # type: Column
|
||||||
|
variant = ... # type: Column
|
||||||
|
|
||||||
def __init__(self, **kwargs) -> None:
|
def __init__(self, **kwargs) -> None:
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.id = ... # type: int
|
self.id = ... # type: int
|
||||||
self.type = ... # type: str
|
self.type = ... # type: str
|
||||||
self.hid = ... # type: str
|
self.hid = ... # type: Optional[str]
|
||||||
self.model = ... # type: str
|
self.model = ... # type: Optional[str]
|
||||||
self.manufacturer = ... # type: str
|
self.manufacturer = ... # type: Optional[str]
|
||||||
self.serial_number = ... # type: str
|
self.serial_number = ... # type: Optional[str]
|
||||||
self.weight = ... # type: float
|
self.weight = ... # type: Optional[float]
|
||||||
self.width = ... # type:float
|
self.width = ... # type:Optional[float]
|
||||||
self.height = ... # type: float
|
self.height = ... # type: Optional[float]
|
||||||
self.depth = ... # type: float
|
self.depth = ... # type: Optional[float]
|
||||||
self.color = ... # type: Color
|
self.color = ... # type: Optional[Color]
|
||||||
self.physical_properties = ... # type: Dict[str, object or None]
|
self.physical_properties = ... # type: Dict[str, object or None]
|
||||||
self.events_multiple = ... # type: Set[e.EventWithMultipleDevices]
|
self.events_multiple = ... # type: Set[e.EventWithMultipleDevices]
|
||||||
self.events_one = ... # type: Set[e.EventWithOneDevice]
|
self.events_one = ... # type: Set[e.EventWithOneDevice]
|
||||||
self.images = ... # type: ImageList
|
|
||||||
self.tags = ... # type: Tags[Tag]
|
self.tags = ... # type: Tags[Tag]
|
||||||
self.lots = ... # type: Set[Lot]
|
self.lots = ... # type: Set[Lot]
|
||||||
self.production_date = ... # type: datetime
|
self.production_date = ... # type: Optional[datetime]
|
||||||
|
self.brand = ... # type: Optional[str]
|
||||||
|
self.generation = ... # type: Optional[int]
|
||||||
|
self.variant = ... # type: Optional[str]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def events(self) -> List[e.Event]:
|
def events(self) -> List[e.Event]:
|
||||||
|
@ -96,7 +103,7 @@ class Device(Thing):
|
||||||
def working(self) -> List[e.Test]:
|
def working(self) -> List[e.Test]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def last_event_of(self, *types: Type[e.Event]) -> e.Event:
|
def last_event_of(self, *types: Type[E]) -> E:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _warning_events(self, events: Iterable[e.Event]) -> Generator[e.Event]:
|
def _warning_events(self, events: Iterable[e.Event]) -> Generator[e.Event]:
|
||||||
|
@ -118,9 +125,11 @@ class DisplayMixin:
|
||||||
self.size = ... # type: Integer
|
self.size = ... # type: Integer
|
||||||
self.resolution_width = ... # type: int
|
self.resolution_width = ... # type: int
|
||||||
self.resolution_height = ... # type: int
|
self.resolution_height = ... # type: int
|
||||||
self.refresh_rate = ... # type: int
|
self.refresh_rate = ... # type: Optional[int]
|
||||||
self.contrast_ratio = ... # type: int
|
self.contrast_ratio = ... # type: Optional[int]
|
||||||
self.touchable = ... # type: bool
|
self.touchable = ... # type: Optional[bool]
|
||||||
|
self.aspect_ratio = ... #type: Fraction
|
||||||
|
self.widescreen = ... # type: bool
|
||||||
|
|
||||||
|
|
||||||
class Computer(DisplayMixin, Device):
|
class Computer(DisplayMixin, Device):
|
||||||
|
@ -193,11 +202,15 @@ class TelevisionSet(Monitor):
|
||||||
class Mobile(Device):
|
class Mobile(Device):
|
||||||
imei = ... # type: Column
|
imei = ... # type: Column
|
||||||
meid = ... # type: Column
|
meid = ... # type: Column
|
||||||
|
ram_size = ... # type: Column
|
||||||
|
data_storage_size = ... # type: Column
|
||||||
|
|
||||||
def __init__(self, **kwargs) -> None:
|
def __init__(self, **kwargs) -> None:
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.imei = ... # type: int
|
self.imei = ... # type: Optional[int]
|
||||||
self.meid = ... # type: str
|
self.meid = ... # type: Optional[str]
|
||||||
|
self.ram_size = ... # type: Optional[int]
|
||||||
|
self.data_storage_size = ... # type: Optional[int]
|
||||||
|
|
||||||
|
|
||||||
class Smartphone(Mobile):
|
class Smartphone(Mobile):
|
||||||
|
@ -288,13 +301,15 @@ class Processor(Component):
|
||||||
cores = ... # type: Column
|
cores = ... # type: Column
|
||||||
address = ... # type: Column
|
address = ... # type: Column
|
||||||
threads = ... # type: Column
|
threads = ... # type: Column
|
||||||
|
abi = ... # type: Column
|
||||||
|
|
||||||
def __init__(self, **kwargs) -> None:
|
def __init__(self, **kwargs) -> None:
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.speed = ... # type: float
|
self.speed = ... # type: Optional[float]
|
||||||
self.cores = ... # type: int
|
self.cores = ... # type: Optional[int]
|
||||||
self.threads = ... # type: int
|
self.threads = ... # type: Optional[int]
|
||||||
self.address = ... # type: int
|
self.address = ... # type: Optional[int]
|
||||||
|
self.abi = ... # type: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
class RamModule(Component):
|
class RamModule(Component):
|
||||||
|
@ -319,6 +334,18 @@ class Display(DisplayMixin, Component):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Battery(Component):
|
||||||
|
wireless = ... # type: Column
|
||||||
|
technology = ... # type: Column
|
||||||
|
size = ... # type: Column
|
||||||
|
|
||||||
|
def __init__(self, **kwargs) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.wireless = ... # type: Optional[bool]
|
||||||
|
self.technology = ... # type: Optional[BatteryTechnology]
|
||||||
|
self.size = ... # type: bool
|
||||||
|
|
||||||
|
|
||||||
class ComputerAccessory(Device):
|
class ComputerAccessory(Device):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -22,9 +22,17 @@ class Device(Thing):
|
||||||
many=True,
|
many=True,
|
||||||
collection_class=OrderedSet,
|
collection_class=OrderedSet,
|
||||||
description='A set of tags that identify the device.')
|
description='A set of tags that identify the device.')
|
||||||
model = SanitizedStr(lower=True, validate=Length(max=STR_BIG_SIZE))
|
model = SanitizedStr(lower=True,
|
||||||
manufacturer = SanitizedStr(lower=True, validate=Length(max=STR_SIZE))
|
validate=Length(max=STR_BIG_SIZE),
|
||||||
serial_number = SanitizedStr(lower=True, data_key='serialNumber')
|
description=m.Device.model.comment)
|
||||||
|
manufacturer = SanitizedStr(lower=True,
|
||||||
|
validate=Length(max=STR_SIZE),
|
||||||
|
description=m.Device.manufacturer.comment)
|
||||||
|
serial_number = SanitizedStr(lower=True,
|
||||||
|
validate=Length(max=STR_BIG_SIZE),
|
||||||
|
data_key='serialNumber')
|
||||||
|
brand = SanitizedStr(validate=Length(max=STR_BIG_SIZE), description=m.Device.brand.comment)
|
||||||
|
generation = Integer(validate=Range(1, 100), description=m.Device.generation.comment)
|
||||||
weight = Float(validate=Range(0.1, 5), unit=UnitCodes.kgm, description=m.Device.weight.comment)
|
weight = Float(validate=Range(0.1, 5), unit=UnitCodes.kgm, description=m.Device.weight.comment)
|
||||||
width = Float(validate=Range(0.1, 5), unit=UnitCodes.m, description=m.Device.width.comment)
|
width = Float(validate=Range(0.1, 5), unit=UnitCodes.m, description=m.Device.width.comment)
|
||||||
height = Float(validate=Range(0.1, 5), unit=UnitCodes.m, description=m.Device.height.comment)
|
height = Float(validate=Range(0.1, 5), unit=UnitCodes.m, description=m.Device.height.comment)
|
||||||
|
@ -113,8 +121,8 @@ class Desktop(Computer):
|
||||||
|
|
||||||
|
|
||||||
class Laptop(Computer):
|
class Laptop(Computer):
|
||||||
layout = EnumField(Layouts, description=m.Laptop.layout.comment)
|
|
||||||
__doc__ = m.Laptop.__doc__
|
__doc__ = m.Laptop.__doc__
|
||||||
|
layout = EnumField(Layouts, description=m.Laptop.layout.comment)
|
||||||
|
|
||||||
|
|
||||||
class Server(Computer):
|
class Server(Computer):
|
||||||
|
@ -123,19 +131,22 @@ class Server(Computer):
|
||||||
|
|
||||||
class DisplayMixin:
|
class DisplayMixin:
|
||||||
__doc__ = m.DisplayMixin.__doc__
|
__doc__ = m.DisplayMixin.__doc__
|
||||||
|
size = Float(description=m.DisplayMixin.size.comment, validate=Range(2, 150), required=True)
|
||||||
size = Float(description=m.DisplayMixin.size.comment, validate=Range(2, 150))
|
|
||||||
technology = EnumField(enums.DisplayTech,
|
technology = EnumField(enums.DisplayTech,
|
||||||
description=m.DisplayMixin.technology.comment)
|
description=m.DisplayMixin.technology.comment)
|
||||||
resolution_width = Integer(data_key='resolutionWidth',
|
resolution_width = Integer(data_key='resolutionWidth',
|
||||||
validate=Range(10, 20000),
|
validate=Range(10, 20000),
|
||||||
description=m.DisplayMixin.resolution_width.comment)
|
description=m.DisplayMixin.resolution_width.comment,
|
||||||
|
required=True)
|
||||||
resolution_height = Integer(data_key='resolutionHeight',
|
resolution_height = Integer(data_key='resolutionHeight',
|
||||||
validate=Range(10, 20000),
|
validate=Range(10, 20000),
|
||||||
description=m.DisplayMixin.resolution_height.comment)
|
description=m.DisplayMixin.resolution_height.comment,
|
||||||
|
required=True)
|
||||||
refresh_rate = Integer(data_key='refreshRate', validate=Range(10, 1000))
|
refresh_rate = Integer(data_key='refreshRate', validate=Range(10, 1000))
|
||||||
contrast_ratio = Integer(data_key='contrastRatio', validate=Range(100, 100000))
|
contrast_ratio = Integer(data_key='contrastRatio', validate=Range(100, 100000))
|
||||||
touchable = Boolean(missing=False, description=m.DisplayMixin.touchable.comment)
|
touchable = Boolean(description=m.DisplayMixin.touchable.comment)
|
||||||
|
aspect_ratio = String(dump_only=True, description=m.DisplayMixin.aspect_ratio.__doc__)
|
||||||
|
widescreen = Boolean(dump_only=True, description=m.DisplayMixin.widescreen.__doc__)
|
||||||
|
|
||||||
|
|
||||||
class NetworkMixin:
|
class NetworkMixin:
|
||||||
|
@ -164,6 +175,14 @@ class Mobile(Device):
|
||||||
|
|
||||||
imei = Integer(description=m.Mobile.imei.comment)
|
imei = Integer(description=m.Mobile.imei.comment)
|
||||||
meid = Str(description=m.Mobile.meid.comment)
|
meid = Str(description=m.Mobile.meid.comment)
|
||||||
|
ram_size = Integer(validate=Range(min=128, max=36000),
|
||||||
|
data_key='ramSize',
|
||||||
|
unit=UnitCodes.mbyte,
|
||||||
|
description=m.Mobile.ram_size.comment)
|
||||||
|
data_storage_size = Integer(validate=Range(0, 10 ** 8),
|
||||||
|
data_key='dataStorageSize',
|
||||||
|
description=m.Mobile.data_storage_size)
|
||||||
|
|
||||||
|
|
||||||
@pre_load
|
@pre_load
|
||||||
def convert_check_imei(self, data):
|
def convert_check_imei(self, data):
|
||||||
|
@ -247,6 +266,7 @@ class Processor(Component):
|
||||||
threads = Integer(validate=Range(min=1, max=20), description=m.Processor.threads.comment)
|
threads = Integer(validate=Range(min=1, max=20), description=m.Processor.threads.comment)
|
||||||
address = Integer(validate=OneOf({8, 16, 32, 64, 128, 256}),
|
address = Integer(validate=OneOf({8, 16, 32, 64, 128, 256}),
|
||||||
description=m.Processor.address.comment)
|
description=m.Processor.address.comment)
|
||||||
|
abi = SanitizedStr(lower=True, description=m.Processor.abi.comment)
|
||||||
|
|
||||||
|
|
||||||
class RamModule(Component):
|
class RamModule(Component):
|
||||||
|
@ -268,6 +288,14 @@ class Display(DisplayMixin, Component):
|
||||||
__doc__ = m.Display.__doc__
|
__doc__ = m.Display.__doc__
|
||||||
|
|
||||||
|
|
||||||
|
class Battery(Component):
|
||||||
|
__doc__ = m.Battery
|
||||||
|
|
||||||
|
wireless = Boolean(description=m.Battery.wireless.comment)
|
||||||
|
technology = EnumField(enums.BatteryTechnology, description=m.Battery.technology.comment)
|
||||||
|
size = Integer(required=True, description=m.Battery.size.comment)
|
||||||
|
|
||||||
|
|
||||||
class Manufacturer(Schema):
|
class Manufacturer(Schema):
|
||||||
__doc__ = m.Manufacturer.__doc__
|
__doc__ = m.Manufacturer.__doc__
|
||||||
|
|
||||||
|
|
|
@ -272,6 +272,26 @@ class PrinterTechnology(Enum):
|
||||||
Thermal = 'Thermal'
|
Thermal = 'Thermal'
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class BatteryHealth(Enum):
|
||||||
|
"""The battery health status as in Android."""
|
||||||
|
Cold = 'Cold'
|
||||||
|
Dead = 'Dead'
|
||||||
|
Good = 'Good'
|
||||||
|
Overheat = 'Overheat'
|
||||||
|
OverVoltage = 'OverVoltage'
|
||||||
|
UnspecifiedValue = 'UnspecifiedValue'
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class BatteryTechnology(Enum):
|
||||||
|
"""The technology of the Battery."""
|
||||||
|
LiIon = 'Lithium-ion'
|
||||||
|
NiCad = 'Nickel-Cadmium'
|
||||||
|
NiMH = 'Nickel-metal hydride'
|
||||||
|
Al = 'Alkaline'
|
||||||
|
|
||||||
|
|
||||||
class Severity(IntEnum):
|
class Severity(IntEnum):
|
||||||
"""A flag evaluating the event execution. Ex. failed events
|
"""A flag evaluating the event execution. Ex. failed events
|
||||||
have the value `Severity.Error`. Devicehub uses 4 severity levels:
|
have the value `Severity.Error`. Devicehub uses 4 severity levels:
|
||||||
|
|
|
@ -397,7 +397,9 @@ class ErasePhysical(EraseBasic):
|
||||||
|
|
||||||
|
|
||||||
class Step(db.Model):
|
class Step(db.Model):
|
||||||
erasure_id = Column(UUID(as_uuid=True), ForeignKey(EraseBasic.id), primary_key=True)
|
erasure_id = Column(UUID(as_uuid=True),
|
||||||
|
ForeignKey(EraseBasic.id, ondelete='CASCADE'),
|
||||||
|
primary_key=True)
|
||||||
type = Column(Unicode(STR_SM_SIZE), nullable=False)
|
type = Column(Unicode(STR_SM_SIZE), nullable=False)
|
||||||
num = Column(SmallInteger, primary_key=True)
|
num = Column(SmallInteger, primary_key=True)
|
||||||
severity = Column(teal.db.IntEnum(Severity), default=Severity.Info, nullable=False)
|
severity = Column(teal.db.IntEnum(Severity), default=Severity.Info, nullable=False)
|
||||||
|
@ -559,7 +561,6 @@ class SnapshotRequest(db.Model):
|
||||||
id = Column(UUID(as_uuid=True), ForeignKey(Snapshot.id), primary_key=True)
|
id = Column(UUID(as_uuid=True), ForeignKey(Snapshot.id), primary_key=True)
|
||||||
request = Column(JSON, nullable=False)
|
request = Column(JSON, nullable=False)
|
||||||
snapshot = relationship(Snapshot,
|
snapshot = relationship(Snapshot,
|
||||||
|
|
||||||
backref=backref('request',
|
backref=backref('request',
|
||||||
lazy=True,
|
lazy=True,
|
||||||
uselist=False,
|
uselist=False,
|
||||||
|
@ -668,6 +669,27 @@ class TestMixin:
|
||||||
return Column(UUID(as_uuid=True), ForeignKey(Test.id), primary_key=True)
|
return Column(UUID(as_uuid=True), ForeignKey(Test.id), primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MeasureBattery(TestMixin, Test):
|
||||||
|
"""A sample of the status of the battery.
|
||||||
|
|
||||||
|
Operative Systems keep a record of several aspects of a battery.
|
||||||
|
This is a sample of those.
|
||||||
|
"""
|
||||||
|
size = db.Column(db.Integer, nullable=False)
|
||||||
|
size.comment = """Maximum battery capacity, in mAh."""
|
||||||
|
voltage = db.Column(db.Integer, nullable=False)
|
||||||
|
voltage.comment = """The actual voltage of the battery, in mV."""
|
||||||
|
cycle_count = db.Column(db.Integer)
|
||||||
|
cycle_count.comment = """The number of full charges – discharges
|
||||||
|
cycles.
|
||||||
|
"""
|
||||||
|
health = db.Column(db.Enum(BatteryHealth))
|
||||||
|
health.comment = """The health of the Battery.
|
||||||
|
Only reported in Android.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class TestDataStorage(TestMixin, Test):
|
class TestDataStorage(TestMixin, Test):
|
||||||
"""
|
"""
|
||||||
The act of testing the data storage.
|
The act of testing the data storage.
|
||||||
|
|
|
@ -145,6 +145,14 @@ class Test(EventWithOneDevice):
|
||||||
__doc__ = m.Test.__doc__
|
__doc__ = m.Test.__doc__
|
||||||
|
|
||||||
|
|
||||||
|
class MeasureBattery(Test):
|
||||||
|
__doc__ = m.MeasureBattery.__doc__
|
||||||
|
size = Integer(required=True, description=m.MeasureBattery.size.comment)
|
||||||
|
voltage = Integer(required=True, description=m.MeasureBattery.voltage.comment)
|
||||||
|
cycle_count = Integer(required=True, description=m.MeasureBattery.cycle_count.comment)
|
||||||
|
health = EnumField(enums.BatteryHealth, description=m.MeasureBattery.health.comment)
|
||||||
|
|
||||||
|
|
||||||
class TestDataStorage(Test):
|
class TestDataStorage(Test):
|
||||||
__doc__ = m.TestDataStorage.__doc__
|
__doc__ = m.TestDataStorage.__doc__
|
||||||
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
|
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
|
||||||
|
|
|
@ -5,7 +5,7 @@ click==6.7
|
||||||
click-spinner==0.1.8
|
click-spinner==0.1.8
|
||||||
colorama==0.3.9
|
colorama==0.3.9
|
||||||
colour==0.1.5
|
colour==0.1.5
|
||||||
ereuse-utils[naming, test, session, cli]==0.4.0b21
|
ereuse-utils[naming,test,session,cli]==0.4.0b49
|
||||||
Flask==1.0.2
|
Flask==1.0.2
|
||||||
Flask-Cors==3.0.6
|
Flask-Cors==3.0.6
|
||||||
Flask-SQLAlchemy==2.3.2
|
Flask-SQLAlchemy==2.3.2
|
||||||
|
|
8
setup.py
8
setup.py
|
@ -1,10 +1,8 @@
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from setuptools import find_packages, setup
|
from setuptools import find_packages, setup
|
||||||
|
|
||||||
with open('README.md', encoding='utf8') as f:
|
|
||||||
long_description = f.read()
|
|
||||||
|
|
||||||
test_requires = [
|
test_requires = [
|
||||||
'pytest',
|
'pytest',
|
||||||
'requests_mock'
|
'requests_mock'
|
||||||
|
@ -26,13 +24,13 @@ setup(
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
python_requires='>=3.5.3',
|
python_requires='>=3.5.3',
|
||||||
long_description=long_description,
|
long_description=Path('README.md').read_text('utf8'),
|
||||||
long_description_content_type='text/markdown',
|
long_description_content_type='text/markdown',
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'teal>=0.2.0a38', # teal always first
|
'teal>=0.2.0a38', # teal always first
|
||||||
'click',
|
'click',
|
||||||
'click-spinner',
|
'click-spinner',
|
||||||
'ereuse-utils[naming, test, session, cli]>=0.4b21',
|
'ereuse-utils[naming,test,session,cli]>=0.4b49',
|
||||||
'hashids',
|
'hashids',
|
||||||
'marshmallow_enum',
|
'marshmallow_enum',
|
||||||
'psycopg2-binary',
|
'psycopg2-binary',
|
||||||
|
|
|
@ -116,6 +116,7 @@ def test_physical_properties():
|
||||||
'serial': None,
|
'serial': None,
|
||||||
'firewire': None,
|
'firewire': None,
|
||||||
'manufacturer': 'mr',
|
'manufacturer': 'mr',
|
||||||
|
'bios_date': None
|
||||||
}
|
}
|
||||||
assert pc.physical_properties == {
|
assert pc.physical_properties == {
|
||||||
'model': 'foo',
|
'model': 'foo',
|
||||||
|
@ -453,11 +454,6 @@ def test_computer_monitor():
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason='Make test')
|
|
||||||
def test_computer_with_display():
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def test_manufacturer(user: UserClient):
|
def test_manufacturer(user: UserClient):
|
||||||
m, r = user.get(res='Manufacturer', query=[('search', 'asus')])
|
m, r = user.get(res='Manufacturer', query=[('search', 'asus')])
|
||||||
assert m == {'items': [{'name': 'Asus', 'url': 'https://en.wikipedia.org/wiki/Asus'}]}
|
assert m == {'items': [{'name': 'Asus', 'url': 'https://en.wikipedia.org/wiki/Asus'}]}
|
||||||
|
|
Reference in New Issue