Merge branch 'rate' into devel

This commit is contained in:
Xavier Bustamante Talavera 2019-07-01 11:31:18 +02:00
commit c92d6b3282
46 changed files with 935 additions and 823 deletions

View File

@ -41,8 +41,7 @@ def get_version(ctx, param, value):
@click.group(cls=DevicehubGroup,
context_settings=Devicehub.cli_context_settings,
add_version_option=False,
help="""
Manages the Devicehub of the inventory {}.
help="""Manages the Devicehub of the inventory {}.
Use 'export dhi=xx' to set the inventory that this CLI
manages. For example 'export dhi=db1' and then executing

View File

@ -121,8 +121,7 @@ class Client(TealClient):
class UserClient(Client):
"""
A client that identifies all of its requests with a specific user.
"""A client that identifies all of its requests with a specific user.
It will automatically perform login on the first request.
"""

View File

@ -26,8 +26,7 @@ class DevicehubConfig(Config):
PASSWORD_SCHEMES = {'pbkdf2_sha256'} # type: Set[str]
SQLALCHEMY_DATABASE_URI = 'postgresql://dhub:ereuse@localhost/devicehub' # type: str
MIN_WORKBENCH = StrictVersion('11.0a1') # type: StrictVersion
"""
the minimum version of ereuse.org workbench that this devicehub
"""The minimum version of ereuse.org workbench that this devicehub
accepts. we recommend not changing this value.
"""
API_DOC_CONFIG_TITLE = 'Devicehub'
@ -42,6 +41,4 @@ class DevicehubConfig(Config):
PRICE_SOFTWARE = PriceSoftware.Ereuse
PRICE_VERSION = StrictVersion('1.0')
PRICE_CURRENCY = Currency.EUR
"""
Official versions
"""
"""Official versions."""

View File

@ -27,8 +27,7 @@ class DhSession(SchemaSession):
class SQLAlchemy(SchemaSQLAlchemy):
"""
Superuser must create the required extensions in the public
"""Superuser must create the required extensions in the public
schema of the database, as it is in the `search_path`
defined in teal.
"""

View File

@ -1,5 +1,4 @@
"""
This file contains all actions can apply to a device and is sorted according
"""This file contains all actions can apply to a device and is sorted according
to a structure based on:
* Generic Actions
@ -72,30 +71,25 @@ class Action(Thing):
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
type = Column(Unicode, nullable=False)
name = Column(CIText(), default='', nullable=False)
name.comment = """
A name or title for the action. Used when searching for actions.
name.comment = """A name or title for the action. Used when searching
for actions.
"""
severity = Column(teal.db.IntEnum(Severity), default=Severity.Info, nullable=False)
severity.comment = Severity.__doc__
closed = Column(Boolean, default=True, nullable=False)
closed.comment = """
Whether the author has finished the action.
closed.comment = """Whether the author has finished the action.
After this is set to True, no modifications are allowed.
By default actions are closed when performed.
"""
description = Column(Unicode, default='', nullable=False)
description.comment = """
A comment about the action.
"""
description.comment = """A comment about the action."""
start_time = Column(db.TIMESTAMP(timezone=True))
start_time.comment = """
When the action starts. For some actions like reservations
the time when they are available, for others like renting
start_time.comment = """When the action starts. For some actions like
reservations the time when they are available, for others like renting
when the renting starts.
"""
end_time = Column(db.TIMESTAMP(timezone=True))
end_time.comment = """
When the action ends. For some actions like reservations
end_time.comment = """When the action ends. For some actions like reservations
the time when they expire, for others like renting
the time the end rents. For punctual actions it is the time
they are performed; it differs with ``created`` in which
@ -120,8 +114,7 @@ class Action(Thing):
author = relationship(User,
backref=backref('authored_actions', lazy=True, collection_class=set),
primaryjoin=author_id == User.id)
author_id.comment = """
The user that recorded this action in the system.
author_id.comment = """The user that recorded this action in the system.
This does not necessarily has to be the person that produced
the action in the real world. For that purpose see
@ -136,8 +129,8 @@ class Action(Thing):
agent = relationship(Agent,
backref=backref('actions_agent', lazy=True, **_sorted_actions),
primaryjoin=agent_id == Agent.id)
agent_id.comment = """
The direct performer or driver of the action. e.g. John wrote a book.
agent_id.comment = """The direct performer or driver of the action.
e.g. John wrote a book.
It can differ with the user that registered the action in the
system, which can be in their behalf.
@ -148,8 +141,7 @@ class Action(Thing):
secondary=lambda: ActionComponent.__table__,
order_by=lambda: Component.id,
collection_class=OrderedSet)
components.comment = """
The components that are affected by the action.
components.comment = """The components that are affected by the action.
When performing actions to parent devices their components are
affected too.
@ -165,9 +157,8 @@ class Action(Thing):
parent = relationship(Computer,
backref=backref('actions_parent', lazy=True, **_sorted_actions),
primaryjoin=parent_id == Computer.id)
parent_id.comment = """
For actions that are performed to components, the device parent
at that time.
parent_id.comment = """For actions that are performed to components,
the device parent at that time.
For example: for a ``EraseBasic`` performed on a data storage, this
would point to the computer that contained this data storage, if any.
@ -197,8 +188,7 @@ class Action(Thing):
# noinspection PyMethodParameters
@declared_attr
def __mapper_args__(cls):
"""
Defines inheritance.
"""Defines inheritance.
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
extensions/declarative/api.html
@ -276,8 +266,7 @@ class ActionWithOneDevice(JoinedTableMixin, Action):
@declared_attr
def __mapper_args__(cls):
"""
Defines inheritance.
"""Defines inheritance.
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
extensions/declarative/api.html
@ -365,7 +354,7 @@ class EraseBasic(JoinedWithOneDeviceMixin, ActionWithOneDevice):
@property
def certificate(self):
"""The URL of this erasure certificate."""
# todo will this url_for_resoure work for other resources?
# todo will this url_for_resource work for other resources?
return urlutils.URL(url_for_resource('Document', item_id=self.id))
def __str__(self) -> str:
@ -430,8 +419,7 @@ class Step(db.Model):
# noinspection PyMethodParameters
@declared_attr
def __mapper_args__(cls):
"""
Defines inheritance.
"""Defines inheritance.
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
extensions/declarative/api.html
@ -545,9 +533,8 @@ class Snapshot(JoinedWithOneDeviceMixin, ActionWithOneDevice):
version = Column(StrictVersionType(STR_SM_SIZE), nullable=False)
software = Column(DBEnum(SnapshotSoftware), nullable=False)
elapsed = Column(Interval)
elapsed.comment = """
For Snapshots made with Workbench, the total amount of time
it took to complete.
elapsed.comment = """For Snapshots made with Workbench, the total amount
of time it took to complete.
"""
def __str__(self) -> str:
@ -578,8 +565,7 @@ class Benchmark(JoinedWithOneDeviceMixin, ActionWithOneDevice):
@declared_attr
def __mapper_args__(cls):
"""
Defines inheritance.
"""Defines inheritance.
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
extensions/declarative/api.html
@ -656,8 +642,7 @@ class Test(JoinedWithOneDeviceMixin, ActionWithOneDevice):
@declared_attr
def __mapper_args__(cls):
"""
Defines inheritance.
"""Defines inheritance.
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
extensions/declarative/api.html
@ -684,6 +669,11 @@ class MeasureBattery(TestMixin, Test):
Operative Systems keep a record of several aspects of a battery.
This is a sample of those.
Failing and warning conditions are as follows:
* :attr:`Severity.Error`: whether the health are Dead, Overheat or OverVoltage.
* :attr:`Severity.Warning`: whether the health are UnspecifiedValue or Cold.
"""
size = db.Column(db.Integer, nullable=False)
size.comment = """Maximum battery capacity, in mAh."""
@ -712,7 +702,7 @@ class TestDataStorage(TestMixin, Test):
Failing and warning conditions are as follows:
* :attr:`Severity.Error`: if the SMART test failed.
* :attr:`Severity.Error`: whether the SMART test failed.
* :attr:`Severity.Warning`: if there is a significant chance for
the data storage to fail in the following year.
"""
@ -765,6 +755,11 @@ class TestDataStorage(TestMixin, Test):
class StressTest(TestMixin, Test):
"""The act of stressing (putting to the maximum capacity)
a device for an amount of minutes.
Failing and warning conditions are as follows:
* :attr:`Severity.Error`: whether failed StressTest.
* :attr:`Severity.Warning`: if stress test are less than 5 minutes.
"""
elapsed = Column(Interval, nullable=False)
@ -780,7 +775,13 @@ class StressTest(TestMixin, Test):
class TestAudio(TestMixin, Test):
"""The act of checking the audio aspects of the device."""
"""The act of checking the audio aspects of the device.
Failing and warning conditions are as follows:
* :attr:`Severity.Error`: whether speaker or microphone variables fail.
* :attr:`Severity.Warning`: .
"""
_speaker = Column('speaker', Boolean)
_speaker.comment = """Whether the speaker works as expected."""
_microphone = Column('microphone', Boolean)
@ -824,9 +825,9 @@ class TestCamera(TestMixin, Test):
Failing and warning conditions are as follows:
* :attr:`Severity.Error`: if the camera cannot turn on or
* :attr:`Severity.Error`: whether the camera cannot turn on or
has significant visual problems.
* :attr:`Severity.Warning`: if there are small visual problems
* :attr:`Severity.Warning`: whether there are small visual problems
with the camera (like dust) that it still allows it to be used.
"""
@ -856,7 +857,7 @@ class TestDisplayHinge(TestMixin, Test):
Failing and warning conditions are as follows:
* :attr:`Severity.Error`: if the laptop does not stay open
* :attr:`Severity.Error`: whether the laptop does not stay open
or closed at desired angles. From R2 Provision 6 pag.22.
"""
@ -872,7 +873,13 @@ class TestPowerAdapter(TestMixin, Test):
class TestBios(TestMixin, Test):
"""Tests the working condition and grades the usability of the BIOS."""
"""Tests the working condition and grades the usability of the BIOS.
Failing and warning conditions are as follows:
* :attr:`Severity.Error`: whether Bios beeps or access range is D or E.
* :attr:`Severity.Warning`: whether access range is B or C.
"""
beeps_power_on = Column(Boolean)
beeps_power_on.comment = """Whether there are no beeps or error
codes when booting up.
@ -884,7 +891,8 @@ class TestBios(TestMixin, Test):
This is used as an usability measure for accessing and modifying
a bios, specially as something as important as modifying the boot
menu."""
menu.
"""
class VisualTest(TestMixin, Test):
@ -893,7 +901,16 @@ class VisualTest(TestMixin, Test):
Reference R2 provision 6 Templates Ready for Resale Checklist (Desktop)
https://sustainableelectronics.org/sites/default/files/6.c.2%20Desktop%20R2-Ready%20for%20Resale%20Checklist.docx
Physical condition grade
Physical condition grade.
Failing and warning conditions are as follows:
* :attr:`Severity.Error`: whether appearance range is less than B or
functionality range is less than B.
* :attr:`Severity.Warning`: whether appearance range is B or A and
functionality range is B.
* :attr:`Severity.Info`: whether appearance range is B or A and
functionality range is A.
"""
appearance_range = Column(DBEnum(AppearanceRange), nullable=False)
appearance_range.comment = AppearanceRange.__doc__
@ -916,7 +933,6 @@ class Rate(JoinedWithOneDeviceMixin, ActionWithOneDevice):
* Appearance (A). Visual evaluation, surface deterioration.
* Performance (Q). Components characteristics and components benchmarks.
"""
# todo jn: explain in each comment what the rate considers.
N = 2
"""The number of significant digits for rates.
Values are rounded and stored to it.
@ -929,11 +945,11 @@ class Rate(JoinedWithOneDeviceMixin, ActionWithOneDevice):
_appearance = Column('appearance',
Float(decimal_return_scale=N),
check_range('appearance', *R_NEGATIVE))
_appearance.comment = """"""
_appearance.comment = """Subjective value representing aesthetic aspects."""
_functionality = Column('functionality',
Float(decimal_return_scale=N),
check_range('functionality', *R_NEGATIVE))
_functionality.comment = """"""
_functionality.comment = """Subjective value representing usage aspects."""
@property
def rating(self):
@ -966,8 +982,7 @@ class Rate(JoinedWithOneDeviceMixin, ActionWithOneDevice):
@declared_attr
def __mapper_args__(cls):
"""
Defines inheritance.
"""Defines inheritance.
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
extensions/declarative/api.html
@ -993,10 +1008,9 @@ class RateMixin:
class RateComputer(RateMixin, Rate):
"""
The act of rating a computer type devices.
"""The act of rating a computer type devices.
It's the starting point for calculating the rate.
Algorithm explained in v1.0 file
Algorithm explained in v1.0 file.
"""
_processor = Column('processor',
Float(decimal_return_scale=Rate.N),
@ -1063,9 +1077,7 @@ class RateComputer(RateMixin, Rate):
@classmethod
def compute(cls, device):
"""
The act of compute general computer rate
"""
"""The act of compute general computer rate."""
from ereuse_devicehub.resources.action.rate.v1_0 import rate_algorithm
rate = rate_algorithm.compute(device)
price = None
@ -1075,6 +1087,7 @@ class RateComputer(RateMixin, Rate):
class Price(JoinedWithOneDeviceMixin, ActionWithOneDevice):
# TODO rewrite Class comment change AggregateRate..
"""The act of setting a trading price for the device.
This does not imply that the device is ultimately traded for that
@ -1101,7 +1114,8 @@ class Price(JoinedWithOneDeviceMixin, ActionWithOneDevice):
version.comment = """The version of the software, or None."""
rating_id = Column(UUID(as_uuid=True), ForeignKey(Rate.id))
rating_id.comment = """The Rate used to auto-compute
this price, if it has not been set manually."""
this price, if it has not been set manually.
"""
rating = relationship(Rate,
backref=backref('price',
lazy=True,
@ -1125,8 +1139,7 @@ class Price(JoinedWithOneDeviceMixin, ActionWithOneDevice):
@declared_attr
def __mapper_args__(cls):
"""
Defines inheritance.
"""Defines inheritance.
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
extensions/declarative/api.html
@ -1216,9 +1229,8 @@ class EreusePrice(Price):
@orm.reconstructor
def _compute(self):
"""
Calculates eReuse.org prices when initializing the
instance from the price and other properties.
"""Calculates eReuse.org prices when initializing the instance
from the price and other properties.
"""
self.refurbisher = self._service(self.Service.REFURBISHER)
self.retailer = self._service(self.Service.RETAILER)
@ -1335,20 +1347,16 @@ class Trade(JoinedTableMixin, ActionWithMultipleDevices):
extend `Schema's Trade <http://schema.org/TradeAction>`_.
"""
shipping_date = Column(db.TIMESTAMP(timezone=True))
shipping_date.comment = """
When are the devices going to be ready for shipping?
shipping_date.comment = """When are the devices going to be ready
for shipping?
"""
invoice_number = Column(CIText())
invoice_number.comment = """
The id of the invoice so they can be linked.
"""
invoice_number.comment = """The id of the invoice so they can be linked."""
price_id = Column(UUID(as_uuid=True), ForeignKey(Price.id))
price = relationship(Price,
backref=backref('trade', lazy=True, uselist=False),
primaryjoin=price_id == Price.id)
price_id.comment = """
The price set for this trade.
price_id.comment = """The price set for this trade.
If no price is set it is supposed that the trade was
not payed, usual in donations.
"""
@ -1357,15 +1365,12 @@ class Trade(JoinedTableMixin, ActionWithMultipleDevices):
to = relationship(Agent,
backref=backref('actions_to', lazy=True, **_sorted_actions),
primaryjoin=to_id == Agent.id)
to_comment = """
The agent that gets the device due this deal.
"""
to_comment = """The agent that gets the device due this deal."""
confirms_id = Column(UUID(as_uuid=True), ForeignKey(Organize.id))
confirms = relationship(Organize,
backref=backref('confirmation', lazy=True, uselist=False),
primaryjoin=confirms_id == Organize.id)
confirms_id.comment = """
An organize action that this association confirms.
confirms_id.comment = """An organize action that this association confirms.
For example, a ``Sell`` or ``Rent``
can confirm a ``Reserve`` action.
@ -1482,8 +1487,7 @@ def actions_not_for_components(target: Action, value: Device, old_value, initiat
@event.listens_for(ActionWithOneDevice.device, Events.set.__name__, propagate=True)
def update_components_action_one(target: ActionWithOneDevice, device: Device, __, ___):
"""
Syncs the :attr:`.Action.components` with the components in
"""Syncs the :attr:`.Action.components` with the components in
:attr:`ereuse_devicehub.resources.device.models.Computer.components`.
"""
# For Add and Remove, ``components`` have different meanings
@ -1500,8 +1504,7 @@ def update_components_action_one(target: ActionWithOneDevice, device: Device, __
@event.listens_for(ActionWithMultipleDevices.devices, Events.append.__name__, propagate=True)
def update_components_action_multiple(target: ActionWithMultipleDevices,
value: Union[Set[Device], Device], _):
"""
Syncs the :attr:`.Action.components` with the components in
"""Syncs the :attr:`.Action.components` with the components in
:attr:`ereuse_devicehub.resources.device.models.Computer.components`.
"""
target.components.clear()
@ -1513,8 +1516,7 @@ def update_components_action_multiple(target: ActionWithMultipleDevices,
@event.listens_for(ActionWithMultipleDevices.devices, Events.remove.__name__, propagate=True)
def remove_components_action_multiple(target: ActionWithMultipleDevices, device: Device, __):
"""
Syncs the :attr:`.Action.components` with the components in
"""Syncs the :attr:`.Action.components` with the components in
:attr:`ereuse_devicehub.resources.device.models.Computer.components`.
"""
target.components.clear()
@ -1528,9 +1530,7 @@ def remove_components_action_multiple(target: ActionWithMultipleDevices, device:
@event.listens_for(Install.device, Events.set.__name__, propagate=True)
@event.listens_for(Benchmark.device, Events.set.__name__, propagate=True)
def update_parent(target: Union[EraseBasic, Test, Install], device: Device, _, __):
"""
Syncs the :attr:`Action.parent` with the parent of the device.
"""
"""Syncs the :attr:`Action.parent` with the parent of the device."""
target.parent = None
if isinstance(device, Component):
target.parent = device.parent

View File

@ -1,22 +1,23 @@
import math
from typing import Iterable
import math
from ereuse_devicehub.resources.device.models import Device
class BaseRate:
"""growing exponential from this value"""
"""Growing exponential from this value."""
CEXP = 0
"""growing lineal starting on this value"""
"""Growing lineal starting on this value."""
CLIN = 242
"""growing logarithmic starting on this value"""
"""Growing logarithmic starting on this value."""
CLOG = 0.5
"""Processor has 50% of weight over total score, used in harmonic mean"""
"""Processor has 50% of weight over total score, used in harmonic mean."""
PROCESSOR_WEIGHT = 0.5
"""Storage has 20% of weight over total score, used in harmonic mean"""
"""Storage has 20% of weight over total score, used in harmonic mean."""
DATA_STORAGE_WEIGHT = 0.2
"""Ram has 30% of weight over total score, used in harmonic mean"""
"""Ram has 30% of weight over total score, used in harmonic mean."""
RAM_WEIGHT = 0.3
def compute(self, device: Device):
@ -43,9 +44,7 @@ class BaseRate:
return sum(weights) / sum(char / rate for char, rate in zip(weights, rates))
def harmonic_mean_rates(self, rate_processor, rate_storage, rate_ram):
"""
Merging components
"""
"""Merging components using harmonic formula."""
total_weights = self.PROCESSOR_WEIGHT + self.DATA_STORAGE_WEIGHT + self.RAM_WEIGHT
total_rate = self.PROCESSOR_WEIGHT / rate_processor \
+ self.DATA_STORAGE_WEIGHT / rate_storage \

View File

@ -13,14 +13,15 @@ class RateAlgorithm(BaseRate):
"""The algorithm that generates the Rate v1.0.
Rate v1.0 rates only computers, counting their processor, ram,
data storage, appearance, and functionality. This rate is only
data storage, appearance and functionality. This rate is only
triggered by a Snapshot from Workbench that has a VisualTest.
The algorithm is as follows:
1. Specialized subclasses of :class:`BaseRate` compute a rating
for each component. To perform this, each class normalizes first
the characteristics and benchmarks of the components between
0 and 1, and then they merge the values to a resulting score.
0 and 1, and then they merge the values with specific formulas for
each components to get a resulting score.
The classes are:
* :class:`ProcessorRate`, using cores, speed, and ``BenchmarkProcessor``.
@ -103,9 +104,7 @@ class RateAlgorithm(BaseRate):
class ProcessorRate(BaseRate):
"""
Calculate a ProcessorRate of all Processor devices
"""
"""Calculate a ProcessorRate of all Processor devices."""
# processor.xMin, processor.xMax
PROCESSOR_NORM = 3196.17, 17503.81
@ -115,8 +114,8 @@ class ProcessorRate(BaseRate):
DEFAULT_SCORE = 4000
def compute(self, processor: Processor):
""" Compute processor rate
We assume always exists a Benchmark Processor
"""Compute processor rate
We assume always exists a Benchmark Processor.
Obs: cores and speed are possible NULL value
:return: result is a rate (score) of Processor characteristics
"""
@ -146,9 +145,7 @@ class ProcessorRate(BaseRate):
class RamRate(BaseRate):
"""
Calculate a RamRate of all RamModule devices
"""
"""Calculate a RamRate of all RamModule devices."""
# ram.size.xMin; ram.size.xMax
SIZE_NORM = 256, 8192
RAM_SPEED_NORM = 133, 1333
@ -158,8 +155,7 @@ class RamRate(BaseRate):
RAM_WEIGHTS = 0.7, 0.3
def compute(self, ram_devices: Iterable[RamModule]):
"""
If ram speed or ram size, we assume default values before declared
"""If ram speed or ram size, we assume default values before declared.
:return: result is a rate (score) of all RamModule components
"""
size = 0.0
@ -204,9 +200,7 @@ class RamRate(BaseRate):
class DataStorageRate(BaseRate):
"""
Calculate the rate of all DataStorage devices
"""
"""Calculate the rate of all DataStorage devices."""
# drive.size.xMin; drive.size.xMax
SIZE_NORM = 4, 265000
READ_SPEED_NORM = 2.7, 109.5
@ -215,8 +209,7 @@ class DataStorageRate(BaseRate):
DATA_STORAGE_WEIGHTS = 0.5, 0.25, 0.25
def compute(self, data_storage_devices: Iterable[DataStorage]):
"""
Obs: size != NULL and 0 value & read_speed and write_speed != NULL
"""Obs: size != NULL and 0 value & read_speed and write_speed != NULL
:return: result is a rate (score) of all DataStorage devices
"""
size = 0

View File

@ -46,8 +46,7 @@ class ActionView(View):
return self.schema.jsonify(action)
def snapshot(self, snapshot_json: dict, resource_def):
"""
Performs a Snapshot.
"""Performs a Snapshot.
See `Snapshot` section in docs for more info.
"""

View File

@ -29,18 +29,13 @@ class Agent(Thing):
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
type = Column(Unicode, nullable=False)
name = Column(CIText())
name.comment = """
The name of the organization or person.
"""
name.comment = """The name of the organization or person."""
tax_id = Column(Unicode(length=STR_SM_SIZE), check_lower('tax_id'))
tax_id.comment = """
The Tax / Fiscal ID of the organization,
tax_id.comment = """The Tax / Fiscal ID of the organization,
e.g. the TIN in the US or the CIF/NIF in Spain.
"""
country = Column(DBEnum(enums.Country))
country.comment = """
Country issuing the tax_id number.
"""
country.comment = """Country issuing the tax_id number."""
telephone = Column(PhoneNumberType())
email = Column(EmailType, unique=True)
@ -52,8 +47,7 @@ class Agent(Thing):
@declared_attr
def __mapper_args__(cls):
"""
Defines inheritance.
"""Defines inheritance.
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
extensions/declarative/api.html
@ -137,8 +131,7 @@ class Membership(Thing):
class Person(Individual):
"""
A person in the system. There can be several persons pointing to
"""A person in the system. There can be several persons pointing to
a real.
"""
pass

View File

@ -44,18 +44,15 @@ class Device(Thing):
(it is a recursive relationship).
"""
id = Column(BigInteger, Sequence('device_seq'), primary_key=True)
id.comment = """
The identifier of the device for this database. Used only
id.comment = """The identifier of the device for this database. Used only
internally for software; users should not use this.
"""
type = Column(Unicode(STR_SM_SIZE), nullable=False)
hid = Column(Unicode(), check_lower('hid'), unique=True)
hid.comment = """
The Hardware ID (HID) is the unique ID traceability systems
use to ID a device globally. This field is auto-generated
hid.comment = """The Hardware ID (HID) is the unique ID traceability
systems use to ID a device globally. This field is auto-generated
from Devicehub using literal identifiers from the device,
so it can re-generated *offline*.
""" + HID_CONVERSION_DOC
model = Column(Unicode, check_lower('model'))
model.comment = """The model of the device in lower case.
@ -84,21 +81,13 @@ class Device(Thing):
version = db.Column(db.CIText())
version.comment = """The version code this device, like v1 or A001."""
weight = Column(Float(decimal_return_scale=3), check_range('weight', 0.1, 5))
weight.comment = """
The weight of the device.
"""
weight.comment = """The weight of the device."""
width = Column(Float(decimal_return_scale=3), check_range('width', 0.1, 5))
width.comment = """
The width of the device in meters.
"""
width.comment = """The width of the device in meters."""
height = Column(Float(decimal_return_scale=3), check_range('height', 0.1, 5))
height.comment = """
The height of the device in meters.
"""
height.comment = """The height of the device in meters."""
depth = Column(Float(decimal_return_scale=3), check_range('depth', 0.1, 5))
depth.comment = """
The depth of the device in meters.
"""
depth.comment = """The depth of the device in meters."""
color = Column(ColorType)
color.comment = """The predominant color of the device."""
production_date = Column(db.DateTime)
@ -146,8 +135,7 @@ class Device(Thing):
@property
def actions(self) -> list:
"""
All the actions where the device participated, including:
"""All the actions where the device participated, including:
1. Actions performed directly to the device.
2. Actions performed to a component.
@ -177,8 +165,7 @@ class Device(Thing):
@property
def physical_properties(self) -> Dict[str, object or None]:
"""
Fields that describe the physical properties of a device.
"""Fields that describe the physical properties of a device.
:return A generator where each value is a tuple with tho fields:
- Column.
@ -266,8 +253,7 @@ class Device(Thing):
@declared_attr
def __mapper_args__(cls):
"""
Defines inheritance.
"""Defines inheritance.
From `the guide <http://docs.sqlalchemy.org/en/latest/orm/
extensions/declarative/api.html
@ -314,24 +300,20 @@ class Device(Thing):
class DisplayMixin:
"""Base class for the Display Component and the Monitor Device."""
size = Column(Float(decimal_return_scale=1), check_range('size', 2, 150), nullable=False)
size.comment = """
The size of the monitor in inches.
"""
size.comment = """The size of the monitor in inches."""
technology = Column(DBEnum(DisplayTech))
technology.comment = """
The technology the monitor uses to display the image.
technology.comment = """The technology the monitor uses to display
the image.
"""
resolution_width = Column(SmallInteger, check_range('resolution_width', 10, 20000),
nullable=False)
resolution_width.comment = """
The maximum horizontal resolution the monitor can natively support
in pixels.
resolution_width.comment = """The maximum horizontal resolution the
monitor can natively support in pixels.
"""
resolution_height = Column(SmallInteger, check_range('resolution_height', 10, 20000),
nullable=False)
resolution_height.comment = """
The maximum vertical resolution the monitor can natively support
in pixels.
resolution_height.comment = """The maximum vertical resolution the
monitor can natively support in pixels.
"""
refresh_rate = Column(SmallInteger, check_range('refresh_rate', 10, 1000))
contrast_ratio = Column(SmallInteger, check_range('contrast_ratio', 100, 100000))
@ -467,7 +449,8 @@ class Desktop(Computer):
class Laptop(Computer):
layout = Column(DBEnum(Layouts))
layout.comment = """Layout of a built-in keyboard of the computer,
if any."""
if any.
"""
class Server(Computer):
@ -549,11 +532,11 @@ class Component(Device):
)
def similar_one(self, parent: Computer, blacklist: Set[int]) -> 'Component':
"""
Gets a component that:
- has the same parent.
- Doesn't generate HID.
- Has same physical properties.
"""Gets a component that:
* has the same parent.
* Doesn't generate HID.
* Has same physical properties.
:param parent:
:param blacklist: A set of components to not to consider
when looking for similar ones.
@ -580,17 +563,13 @@ class JoinedComponentTableMixin:
class GraphicCard(JoinedComponentTableMixin, Component):
memory = Column(SmallInteger, check_range('memory', min=1, max=10000))
memory.comment = """
The amount of memory of the Graphic Card in MB.
"""
memory.comment = """The amount of memory of the Graphic Card in MB."""
class DataStorage(JoinedComponentTableMixin, Component):
"""A device that stores information."""
size = Column(Integer, check_range('size', min=1, max=10 ** 8))
size.comment = """
The size of the data-storage in MB.
"""
size.comment = """The size of the data-storage in MB."""
interface = Column(DBEnum(DataStorageInterface))
@property
@ -623,9 +602,7 @@ class SolidStateDrive(DataStorage):
class Motherboard(JoinedComponentTableMixin, Component):
slots = Column(SmallInteger, check_range('slots', min=0))
slots.comment = """
PCI slots the motherboard has.
"""
slots.comment = """PCI slots the motherboard has."""
usb = Column(SmallInteger, check_range('usb', min=0))
firewire = Column(SmallInteger, check_range('firewire', min=0))
serial = Column(SmallInteger, check_range('serial', min=0))
@ -638,13 +615,11 @@ class Motherboard(JoinedComponentTableMixin, Component):
class NetworkMixin:
speed = Column(SmallInteger, check_range('speed', min=10, max=10000))
speed.comment = """
The maximum speed this network adapter can handle, in mbps.
speed.comment = """The maximum speed this network adapter can handle,
in mbps.
"""
wireless = Column(Boolean, nullable=False, default=False)
wireless.comment = """
Whether it is a wireless interface.
"""
wireless.comment = """Whether it is a wireless interface."""
def __format__(self, format_spec):
v = super().__format__(format_spec)
@ -685,8 +660,7 @@ class SoundCard(JoinedComponentTableMixin, Component):
class Display(JoinedComponentTableMixin, DisplayMixin, Component):
"""
The display of a device. This is used in all devices that have
"""The display of a device. This is used in all devices that have
displays but that it is not their main part, like laptops,
mobiles, smart-watches, and so on; excluding ``ComputerMonitor``
and ``TelevisionSet``.

View File

@ -20,8 +20,7 @@ class State(Enum):
class Trading(State):
"""
Trading states.
"""Trading states.
:cvar Reserved: The device has been reserved.
:cvar Cancelled: The device has been cancelled.
@ -44,8 +43,7 @@ class Trading(State):
class Physical(State):
"""
Physical states.
"""Physical states.
:cvar ToBeRepaired: The device has been selected for reparation.
:cvar Repaired: The device has been repaired.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -1,3 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC
"-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns:serif="http://www.serif.com/" width="100%" height="100%" viewBox="0 0 46 46" version="1.1" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;"><rect id="Photochromic-alone" serif:id="Photochromic alone" x="0" y="0" width="45.84" height="45.84" style="fill:none;"/><path d="M43.96,23.198c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z" style="fill:#fff;"/><clipPath id="_clip1"><path d="M43.96,23.198c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z"/></clipPath><g clip-path="url(#_clip1)"><clipPath id="_clip2"><rect x="4.275" y="4.975" width="36.85" height="36.85"/></clipPath><g clip-path="url(#_clip2)"><g id="Artboard1"><rect x="4.275" y="4.975" width="36.71" height="36.701" style="fill:none;"/><g id="Logo-01"><path d="M6.749,38.053c4.02,-13.193 12.218,-7.047 23.011,-10.325c17.626,-5.353 -0.052,-29.186 -15.117,-16.725c-15.241,12.606 -3.74,38.876 19.597,23.634" style="fill:none;stroke:#bfff04;stroke-width:4.6px;"/><path d="M36.496,29.706l2.816,5.333l-6.526,4.015c0,0 -1.033,0.738 -1.598,-0.147c-0.565,-0.887 -1.553,-2.671 -2.028,-3.741c-0.364,-0.817 0.661,-1.324 0.661,-1.324l6.675,-4.136Z" style="fill:#bfff04;fill-rule:nonzero;stroke:#bfff04;stroke-width:0.17px;stroke-linecap:butt;stroke-miterlimit:1.41421;"/><path d="M39.031,33.89l-2.012,-3.698l1.953,-1.153l2.012,3.699l-1.953,1.152Z" style="fill:#bfff04;fill-rule:nonzero;stroke:#bfff04;stroke-width:0.17px;stroke-linecap:butt;stroke-miterlimit:1.41421;"/></g></g></g></g><path d="M43.96,23.198c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z" style="fill:none;stroke:#333;stroke-width:0.1px;stroke-linejoin:miter;stroke-miterlimit:11;"/></svg>
<svg xmlns:serif="http://www.serif.com/" width="100%" height="100%" viewBox="0 0 46 46"
version="1.1" xmlns="http://www.w3.org/2000/svg" xml:space="preserve"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;"><rect id="Photochromic-alone" serif:id="Photochromic alone" x="0" y="0" width="45.84" height="45.84" style="fill:none;"/>
<path d="M43.96,23.198c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z"
style="fill:#fff;"/>
<clipPath id="_clip1"><path d="M43.96,23.198c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z"/></clipPath>
<g clip-path="url(#_clip1)"><clipPath id="_clip2"><rect x="4.275" y="4.975" width="36.85" height="36.85"/></clipPath>
<g clip-path="url(#_clip2)"><g id="Artboard1"><rect x="4.275" y="4.975" width="36.71" height="36.701" style="fill:none;"/><g
id="Logo-01"><path d="M6.749,38.053c4.02,-13.193 12.218,-7.047 23.011,-10.325c17.626,-5.353 -0.052,-29.186 -15.117,-16.725c-15.241,12.606 -3.74,38.876 19.597,23.634" style="fill:none;stroke:#bfff04;stroke-width:4.6px;"/>
<path d="M36.496,29.706l2.816,5.333l-6.526,4.015c0,0 -1.033,0.738 -1.598,-0.147c-0.565,-0.887 -1.553,-2.671 -2.028,-3.741c-0.364,-0.817 0.661,-1.324 0.661,-1.324l6.675,-4.136Z"
style="fill:#bfff04;fill-rule:nonzero;stroke:#bfff04;stroke-width:0.17px;stroke-linecap:butt;stroke-miterlimit:1.41421;"/>
<path d="M39.031,33.89l-2.012,-3.698l1.953,-1.153l2.012,3.699l-1.953,1.152Z"
style="fill:#bfff04;fill-rule:nonzero;stroke:#bfff04;stroke-width:0.17px;stroke-linecap:butt;stroke-miterlimit:1.41421;"/></g></g></g></g>
<path d="M43.96,23.198c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z"
style="fill:none;stroke:#333;stroke-width:0.1px;stroke-linejoin:miter;stroke-miterlimit:11;"/></svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -1,3 +1,34 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC
"-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns:serif="http://www.serif.com/" width="100%" height="100%" viewBox="0 0 124 44" version="1.1" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-miterlimit:11;"><rect id="Photochromic-Tag-web" serif:id="Photochromic Tag web" x="0" y="0" width="123.413" height="43.733" style="fill:none;"/><clipPath id="_clip1"><rect x="0" y="0" width="123.413" height="43.733"/></clipPath><g clip-path="url(#_clip1)"><g><path d="M44.175,28.681l0,-4.925l24.55,0l0,-2.496l10.513,4.959l-10.513,4.958l0,-2.496l-24.55,0Z" style="fill:#333;stroke:#333;stroke-width:0.85px;"/><text x="49.508px" y="19.727px" style="font-family:'Lato-Regular', 'Lato', sans-serif;font-size:24.107px;fill:#231f20;">6s</text></g><path d="M42.52,21.058c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z" style="fill:#fff;"/><clipPath id="_clip2"><path d="M42.52,21.058c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z"/></clipPath><g clip-path="url(#_clip2)"><clipPath id="_clip3"><rect x="2.835" y="2.835" width="36.85" height="36.85"/></clipPath><g clip-path="url(#_clip3)"><g id="Artboard1"><rect x="2.835" y="2.835" width="36.71" height="36.701" style="fill:none;"/><g id="Logo-01"><path d="M5.309,35.913c4.02,-13.194 12.218,-7.047 23.011,-10.325c17.626,-5.353 -0.052,-29.186 -15.117,-16.725c-15.241,12.605 -3.74,38.876 19.597,23.634" style="fill:none;stroke:#bfff04;stroke-width:4.6px;stroke-linejoin:round;stroke-miterlimit:10;"/><path d="M35.056,27.566l2.816,5.333l-6.526,4.014c0,0 -1.033,0.739 -1.598,-0.147c-0.565,-0.886 -1.553,-2.67 -2.028,-3.74c-0.364,-0.817 0.661,-1.324 0.661,-1.324l6.675,-4.136Z" style="fill:#bfff04;fill-rule:nonzero;stroke:#bfff04;stroke-width:0.17px;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:1.41421;"/><path d="M37.591,31.75l-2.012,-3.699l1.953,-1.152l2.012,3.699l-1.953,1.152Z" style="fill:#bfff04;fill-rule:nonzero;stroke:#bfff04;stroke-width:0.17px;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:1.41421;"/></g></g></g></g><path d="M42.52,21.058c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z" style="fill:none;stroke:#333;stroke-width:0.1px;"/><path d="M123.005,21.058c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z" style="fill:#fff;"/><clipPath id="_clip4"><path d="M123.005,21.058c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z"/></clipPath><g clip-path="url(#_clip4)"><clipPath id="_clip5"><rect x="83.32" y="2.835" width="36.85" height="36.85"/></clipPath><g clip-path="url(#_clip5)"><g id="Artboard11" serif:id="Artboard1"><rect x="83.32" y="2.835" width="36.71" height="36.701" style="fill:none;"/><path d="M114.217,23.7c0.896,-2.192 1.142,-4.517 0.713,-6.743c-1.347,-6.998 -8.906,-11.33 -16.87,-9.668c-7.965,1.662 -13.337,8.693 -11.99,15.691c0.428,2.225 1.516,4.273 3.153,5.936l24.994,-5.216Z" style="fill:#e5f20d;"/><g id="Logo-011" serif:id="Logo-01"><path d="M85.795,35.92c4.019,-13.193 12.217,-7.047 23.01,-10.325c17.626,-5.353 -0.052,-29.186 -15.117,-16.725c-15.241,12.606 -3.74,38.876 19.597,23.634" style="fill:none;stroke:#bfff04;stroke-width:4.6px;stroke-linejoin:round;stroke-miterlimit:10;"/><path d="M115.541,27.573l2.816,5.333l-6.526,4.015c0,0 -1.033,0.738 -1.597,-0.147c-0.565,-0.887 -1.553,-2.671 -2.029,-3.741c-0.363,-0.817 0.661,-1.324 0.661,-1.324l6.675,-4.136Z" style="fill:#bfff04;fill-rule:nonzero;stroke:#bfff04;stroke-width:0.17px;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:1.41421;"/><path d="M118.077,31.757l-2.013,-3.698l1.953,-1.152l2.013,3.698l-1.953,1.152Z" style="fill:#bfff04;fill-rule:nonzero;stroke:#bfff04;stroke-width:0.17px;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:1.41421;"/></g></g></g></g><path d="M123.005,21.058c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z" style="fill:none;stroke:#333;stroke-width:0.1px;"/></g></svg>
<svg xmlns:serif="http://www.serif.com/" width="100%" height="100%" viewBox="0 0 124 44"
version="1.1" xmlns="http://www.w3.org/2000/svg" xml:space="preserve"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-miterlimit:11;"><rect id="Photochromic-Tag-web" serif:id="Photochromic Tag web" x="0" y="0" width="123.413" height="43.733" style="fill:none;"/>
<clipPath id="_clip1"><rect x="0" y="0" width="123.413" height="43.733"/></clipPath>
<g clip-path="url(#_clip1)"><g><path d="M44.175,28.681l0,-4.925l24.55,0l0,-2.496l10.513,4.959l-10.513,4.958l0,-2.496l-24.55,0Z" style="fill:#333;stroke:#333;stroke-width:0.85px;"/><text
x="49.508px" y="19.727px"
style="font-family:'Lato-Regular', 'Lato', sans-serif;font-size:24.107px;fill:#231f20;">6s</text></g>
<path d="M42.52,21.058c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z"
style="fill:#fff;"/>
<clipPath id="_clip2"><path d="M42.52,21.058c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z"/></clipPath>
<g clip-path="url(#_clip2)"><clipPath id="_clip3"><rect x="2.835" y="2.835" width="36.85" height="36.85"/></clipPath>
<g clip-path="url(#_clip3)"><g id="Artboard1"><rect x="2.835" y="2.835" width="36.71" height="36.701" style="fill:none;"/><g
id="Logo-01"><path d="M5.309,35.913c4.02,-13.194 12.218,-7.047 23.011,-10.325c17.626,-5.353 -0.052,-29.186 -15.117,-16.725c-15.241,12.605 -3.74,38.876 19.597,23.634" style="fill:none;stroke:#bfff04;stroke-width:4.6px;stroke-linejoin:round;stroke-miterlimit:10;"/>
<path d="M35.056,27.566l2.816,5.333l-6.526,4.014c0,0 -1.033,0.739 -1.598,-0.147c-0.565,-0.886 -1.553,-2.67 -2.028,-3.74c-0.364,-0.817 0.661,-1.324 0.661,-1.324l6.675,-4.136Z"
style="fill:#bfff04;fill-rule:nonzero;stroke:#bfff04;stroke-width:0.17px;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:1.41421;"/>
<path d="M37.591,31.75l-2.012,-3.699l1.953,-1.152l2.012,3.699l-1.953,1.152Z"
style="fill:#bfff04;fill-rule:nonzero;stroke:#bfff04;stroke-width:0.17px;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:1.41421;"/></g></g></g></g>
<path d="M42.52,21.058c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z"
style="fill:none;stroke:#333;stroke-width:0.1px;"/>
<path d="M123.005,21.058c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z"
style="fill:#fff;"/>
<clipPath id="_clip4"><path d="M123.005,21.058c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z"/></clipPath>
<g clip-path="url(#_clip4)"><clipPath id="_clip5"><rect x="83.32" y="2.835" width="36.85" height="36.85"/></clipPath>
<g clip-path="url(#_clip5)"><g id="Artboard11" serif:id="Artboard1"><rect x="83.32" y="2.835" width="36.71" height="36.701" style="fill:none;"/><path
d="M114.217,23.7c0.896,-2.192 1.142,-4.517 0.713,-6.743c-1.347,-6.998 -8.906,-11.33 -16.87,-9.668c-7.965,1.662 -13.337,8.693 -11.99,15.691c0.428,2.225 1.516,4.273 3.153,5.936l24.994,-5.216Z"
style="fill:#e5f20d;"/><g id="Logo-011" serif:id="Logo-01"><path d="M85.795,35.92c4.019,-13.193 12.217,-7.047 23.01,-10.325c17.626,-5.353 -0.052,-29.186 -15.117,-16.725c-15.241,12.606 -3.74,38.876 19.597,23.634" style="fill:none;stroke:#bfff04;stroke-width:4.6px;stroke-linejoin:round;stroke-miterlimit:10;"/>
<path d="M115.541,27.573l2.816,5.333l-6.526,4.015c0,0 -1.033,0.738 -1.597,-0.147c-0.565,-0.887 -1.553,-2.671 -2.029,-3.741c-0.363,-0.817 0.661,-1.324 0.661,-1.324l6.675,-4.136Z"
style="fill:#bfff04;fill-rule:nonzero;stroke:#bfff04;stroke-width:0.17px;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:1.41421;"/>
<path d="M118.077,31.757l-2.013,-3.698l1.953,-1.152l2.013,3.698l-1.953,1.152Z"
style="fill:#bfff04;fill-rule:nonzero;stroke:#bfff04;stroke-width:0.17px;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:1.41421;"/></g></g></g></g>
<path d="M123.005,21.058c0,-11.622 -9.436,-21.058 -21.058,-21.058l-0.404,0c-11.622,0 -21.058,9.436 -21.058,21.058l0,0.404c0,11.622 9.436,21.058 21.058,21.058l0.404,0c11.622,0 21.058,-9.436 21.058,-21.058l0,-0.404Z"
style="fill:none;stroke:#333;stroke-width:0.1px;"/></g></svg>

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -23,8 +23,7 @@ class Sync:
def run(self,
device: Device,
components: Iterable[Component] or None) -> (Device, OrderedSet):
"""
Synchronizes the device and components with the database.
"""Synchronizes the device and components with the database.
Identifies if the device and components exist in the database
and updates / inserts them as necessary.
@ -79,8 +78,7 @@ class Sync:
component: Component,
blacklist: Set[int],
parent: Computer):
"""
Synchronizes one component to the DB.
"""Synchronizes one component to the DB.
This method is a specialization of :meth:`.execute_register`
but for components that are inside parents.
@ -125,8 +123,7 @@ class Sync:
return db_component, is_new
def execute_register(self, device: Device) -> Device:
"""
Synchronizes one device to the DB.
"""Synchronizes one device to the DB.
This method tries to get an existing device using the HID
or one of the tags, and...
@ -205,8 +202,7 @@ class Sync:
@staticmethod
def merge(device: Device, db_device: Device):
"""
Copies the physical properties of the device to the db_device.
"""Copies the physical properties of the device to the db_device.
This method mutates db_device.
"""
@ -217,8 +213,7 @@ class Sync:
@staticmethod
def add_remove(device: Computer,
components: Set[Component]) -> OrderedSet:
"""
Generates the Add and Remove actions (but doesn't add them to
"""Generates the Add and Remove actions (but doesn't add them to
session).
:param device: A device which ``components`` attribute contains

View File

@ -78,8 +78,7 @@ class DeviceView(View):
page = f.Integer(validate=v.Range(min=1), missing=1)
def get(self, id):
"""
Devices view
"""Devices view
---
description: Gets a device or multiple devices.
parameters:

View File

@ -56,9 +56,7 @@ class DeviceRow(OrderedDict):
self.components()
def components(self):
"""
Function to get all components information of a device
"""
"""Function to get all components information of a device."""
assert isinstance(self.device, d.Computer)
# todo put an input specific order (non alphabetic) & where are a list of types components
for type in sorted(current_app.resources[d.Component.t].subresources_types): # type: str
@ -75,8 +73,8 @@ class DeviceRow(OrderedDict):
i += 1
def fill_component(self, type, i, component=None):
"""
Function to put specific information of components in OrderedDict (csv)
"""Function to put specific information of components
in OrderedDict (csv)
:param type: type of component
:param component: device.components
"""
@ -85,11 +83,13 @@ class DeviceRow(OrderedDict):
self['{} {} Model'.format(type, i)] = component.serial_number if component else ''
self['{} {} Serial Number'.format(type, i)] = component.serial_number if component else ''
""" Particular fields for component GraphicCard """
"""Particular fields for component GraphicCard."""
if isinstance(component, d.GraphicCard):
self['{} {} Memory (MB)'.format(type, i)] = component.memory
""" Particular fields for component DataStorage.t -> (HardDrive, SolidStateDrive) """
"""Particular fields for component DataStorage.t ->
(HardDrive, SolidStateDrive)
"""
if isinstance(component, d.DataStorage):
self['{} {} Size (MB)'.format(type, i)] = component.size
self['{} {} Privacy'.format(type, i)] = component.privacy
@ -109,16 +109,14 @@ class DeviceRow(OrderedDict):
except:
self['{} {} Writing speed'.format(type, i)] = ''
""" Particular fields for component Processor """
"""Particular fields for component Processor."""
if isinstance(component, d.Processor):
self['{} {} Number of cores'.format(type, i)] = component.cores
self['{} {} Speed (GHz)'.format(type, i)] = component.speed
""" Particular fields for component RamModule """
"""Particular fields for component RamModule."""
if isinstance(component, d.RamModule):
self['{} {} Size (MB)'.format(type, i)] = component.size
self['{} {} Speed (MHz)'.format(type, i)] = component.speed
# todo add Display size, ...
# todo add NetworkAdapter speedLink?
# todo add some ComputerAccessories
# todo add Display, NetworkAdapter, etc...

View File

@ -110,11 +110,7 @@ class DevicesDocumentView(DeviceView):
return self.generate_post_csv(query)
def generate_post_csv(self, query):
"""
Get device query and put information in csv format
:param query:
:return:
"""
"""Get device query and put information in csv format."""
data = StringIO()
cw = csv.writer(data)
first = True

View File

@ -24,16 +24,13 @@ class Lot(Thing):
description = db.Column(CIText())
description.comment = """A comment about the lot."""
closed = db.Column(db.Boolean, default=False, nullable=False)
closed.comment = """
A closed lot cannot be modified anymore.
"""
closed.comment = """A closed lot cannot be modified anymore."""
devices = db.relationship(Device,
backref=db.backref('lots', lazy=True, collection_class=set),
secondary=lambda: LotDevice.__table__,
lazy=True,
collection_class=set)
"""
The **children** devices that the lot has.
"""The **children** devices that the lot has.
Note that the lot can have more devices, if they are inside
descendant lots.
@ -67,8 +64,7 @@ class Lot(Thing):
def __init__(self, name: str, closed: bool = closed.default.arg,
description: str = None) -> None:
"""
Initializes a lot
"""Initializes a lot
:param name:
:param closed:
"""
@ -173,9 +169,7 @@ class LotDevice(db.Model):
nullable=False,
default=lambda: g.user.id)
author = db.relationship(User, primaryjoin=author_id == User.id)
author_id.comment = """
The user that put the device in the lot.
"""
author_id.comment = """The user that put the device in the lot."""
class Path(db.Model):
@ -191,9 +185,7 @@ class Path(db.Model):
primaryjoin=Lot.id == lot_id)
path = db.Column(LtreeType, nullable=False)
created = db.Column(db.TIMESTAMP(timezone=True), server_default=db.text('CURRENT_TIMESTAMP'))
created.comment = """
When Devicehub created this.
"""
created.comment = """When Devicehub created this."""
__table_args__ = (
# dag.delete_edge needs to disable internally/temporarily the unique constraint

View File

@ -23,8 +23,7 @@ class LotFormat(Enum):
class LotView(View):
class FindArgs(MarshmallowSchema):
"""
Allowed arguments for the ``find``
"""Allowed arguments for the ``find``
method (GET collection) endpoint
"""
format = EnumField(LotFormat, missing=None)
@ -56,8 +55,7 @@ class LotView(View):
@teal.cache.cache(datetime.timedelta(minutes=5))
def find(self, args: dict):
"""
Gets lots.
"""Gets lots.
By passing the value `UiTree` in the parameter `format`
of the query you get a recursive nested suited for ui-tree::

View File

@ -20,16 +20,14 @@ class Thing(db.Model):
nullable=False,
index=True,
server_default=db.text('CURRENT_TIMESTAMP'))
updated.comment = """
The last time Devicehub recorded a change for this thing.
updated.comment = """The last time Devicehub recorded a change for
this thing.
"""
created = db.Column(db.TIMESTAMP(timezone=True),
nullable=False,
index=True,
server_default=db.text('CURRENT_TIMESTAMP'))
created.comment = """
When Devicehub created this.
"""
created.comment = """When Devicehub created this."""
def __init__(self, **kwargs) -> None:
# We need to set 'created' before sqlalchemy inits the class

View File

@ -39,8 +39,8 @@ class Tag(Thing):
collection_class=set)
"""The organization that issued the tag."""
provider = Column(URL())
provider.comment = """
The tag provider URL. If None, the provider is this Devicehub.
provider.comment = """The tag provider URL. If None, the provider is
this Devicehub.
"""
device_id = Column(BigInteger,
# We don't want to delete the tag on device deletion, only set to null
@ -50,9 +50,8 @@ class Tag(Thing):
primaryjoin=Device.id == device_id)
"""The device linked to this tag."""
secondary = Column(db.CIText(), index=True)
secondary.comment = """
A secondary identifier for this tag. It has the same
constraints as the main one. Only needed in special cases.
secondary.comment = """A secondary identifier for this tag.
It has the same constraints as the main one. Only needed in special cases.
"""
__table_args__ = (
@ -116,7 +115,7 @@ class Tag(Thing):
@classmethod
def is_printable_q(cls):
"""Return a SQLAlchemy filter expression for printable queries"""
"""Return a SQLAlchemy filter expression for printable queries."""
return cls.org_id == Organization.get_default_org_id()
def __repr__(self) -> str:

View File

@ -50,7 +50,7 @@ class TagView(View):
class TagDeviceView(View):
"""Endpoints to work with the device of the tag; /tags/23/device"""
"""Endpoints to work with the device of the tag; /tags/23/device."""
def one(self, id):
"""Gets the device from the tag."""
@ -78,8 +78,7 @@ class TagDeviceView(View):
def get_device_from_tag(id: str):
"""
Gets the device by passing a tag id.
"""Gets the device by passing a tag id.
Example: /tags/23/device.

View File

@ -28,8 +28,7 @@ class User(Thing):
# todo set restriction that user has, at least, one active db
def __init__(self, email, password=None, inventories=None) -> None:
"""
Creates an user.
"""Creates an user.
:param email:
:param password:
:param inventories: A set of Inventory where the user has

View File

@ -29,8 +29,7 @@ class User(Thing):
load_only=(),
dump_only=(),
partial=False):
"""
Instantiates the User.
"""Instantiates the User.
By default we exclude token from both load/dump
so they are not taken / set in normal usage by mistake.

View File

@ -5,11 +5,11 @@ device:
model: d1ml
manufacturer: d1mr
components:
- type: GraphicCard
- type: GraphicCard
serial_number: gc1s
model: gc1ml
manufacturer: gc1mr
- type: RamModule
- type: RamModule
serial_number: rm1s
model: rm1ml
manufacturer: rm1mr

View File

@ -102,8 +102,7 @@ def test_membership_repeating_id():
@pytest.mark.usefixtures(app_context.__name__)
def test_default_org_exists(config: DevicehubConfig):
"""
Ensures that the default organization is created on app
"""Ensures that the default organization is created on app
initialization and that is accessible for the method
:meth:`ereuse_devicehub.resources.user.Organization.get_default_org`.
"""

View File

@ -31,9 +31,7 @@ from tests.conftest import file
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_device_model():
"""
Tests that the correctness of the device model and its relationships.
"""
"""Tests that the correctness of the device model and its relationships."""
pc = d.Desktop(model='p1mo',
manufacturer='p1ma',
serial_number='p1s',
@ -182,8 +180,7 @@ def test_add_remove():
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_run_components_empty():
"""
Syncs a device that has an empty components list. The system should
"""Syncs a device that has an empty components list. The system should
remove all the components from the device.
"""
s = conftest.file('pc-components.db')
@ -200,8 +197,7 @@ def test_sync_run_components_empty():
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_run_components_none():
"""
Syncs a device that has a None components. The system should
"""Syncs a device that has a None components. The system should
keep all the components from the device.
"""
s = conftest.file('pc-components.db')
@ -218,10 +214,7 @@ def test_sync_run_components_none():
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_execute_register_desktop_new_desktop_no_tag():
"""
Syncs a new d.Desktop with HID and without a tag, creating it.
:return:
"""
"""Syncs a new d.Desktop with HID and without a tag, creating it."""
# Case 1: device does not exist on DB
pc = d.Desktop(**conftest.file('pc-components.db')['device'])
db_pc = Sync().execute_register(pc)
@ -230,9 +223,7 @@ def test_sync_execute_register_desktop_new_desktop_no_tag():
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_execute_register_desktop_existing_no_tag():
"""
Syncs an existing d.Desktop with HID and without a tag.
"""
"""Syncs an existing d.Desktop with HID and without a tag."""
pc = d.Desktop(**conftest.file('pc-components.db')['device'])
db.session.add(pc)
db.session.commit()
@ -246,8 +237,7 @@ def test_sync_execute_register_desktop_existing_no_tag():
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_execute_register_desktop_no_hid_no_tag():
"""
Syncs a d.Desktop without HID and no tag.
"""Syncs a d.Desktop without HID and no tag.
This should fail as we don't have a way to identify it.
"""
@ -260,8 +250,7 @@ def test_sync_execute_register_desktop_no_hid_no_tag():
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_execute_register_desktop_tag_not_linked():
"""
Syncs a new d.Desktop with HID and a non-linked tag.
"""Syncs a new d.Desktop with HID and a non-linked tag.
It is OK if the tag was not linked, it will be linked in this process.
"""
@ -279,8 +268,7 @@ def test_sync_execute_register_desktop_tag_not_linked():
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_execute_register_no_hid_tag_not_linked(tag_id: str):
"""
Validates registering a d.Desktop without HID and a non-linked tag.
"""Validates registering a d.Desktop without HID and a non-linked tag.
In this case it is ok still, as the non-linked tag proves that
the d.Desktop was not existing before (otherwise the tag would
@ -302,8 +290,7 @@ def test_sync_execute_register_no_hid_tag_not_linked(tag_id: str):
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_execute_register_tag_does_not_exist():
"""
Ensures not being able to register if the tag does not exist,
"""Ensures not being able to register if the tag does not exist,
even if the device has HID or it existed before.
Tags have to be created before trying to link them through a Snapshot.
@ -315,8 +302,7 @@ def test_sync_execute_register_tag_does_not_exist():
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_execute_register_tag_linked_same_device():
"""
If the tag is linked to the device, regardless if it has HID,
"""If the tag is linked to the device, regardless if it has HID,
the system should match the device through the tag.
(If it has HID it validates both HID and tag point at the same
device, this his checked in ).
@ -336,8 +322,7 @@ def test_sync_execute_register_tag_linked_same_device():
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_execute_register_tag_linked_other_device_mismatch_between_tags():
"""
Checks that sync raises an error if finds that at least two passed-in
"""Checks that sync raises an error if finds that at least two passed-in
tags are not linked to the same device.
"""
pc1 = d.Desktop(**conftest.file('pc-components.db')['device'])
@ -358,8 +343,7 @@ def test_sync_execute_register_tag_linked_other_device_mismatch_between_tags():
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_sync_execute_register_mismatch_between_tags_and_hid():
"""
Checks that sync raises an error if it finds that the HID does
"""Checks that sync raises an error if it finds that the HID does
not point at the same device as the tag does.
In this case we set HID -> pc1 but tag -> pc2
@ -465,7 +449,8 @@ def test_manufacturer(user: UserClient):
@pytest.mark.xfail(reason='Develop functionality')
def test_manufacturer_enforced():
"""Ensures that non-computer devices can submit only
manufacturers from the Manufacturer table."""
manufacturers from the Manufacturer table.
"""
def test_device_properties_format(app: Devicehub, user: UserClient):

View File

@ -56,8 +56,7 @@ def test_device_sort():
@pytest.fixture()
def device_query_dummy(app: Devicehub):
"""
3 computers, where:
"""3 computers, where:
1. s1 Desktop with a Processor
2. s2 Desktop with an SSD

View File

@ -28,7 +28,7 @@ def test_dispatcher_default(dispatcher: PathDispatcher):
def test_dispatcher_return_app(dispatcher: PathDispatcher):
"""The dispatcher returns the correct app for the URL"""
"""The dispatcher returns the correct app for the URL."""
# Note that the dispatcher does not check if the URL points
# to a well-known endpoint for the app.
# Only if can route it to an app. And then the app checks
@ -39,7 +39,7 @@ def test_dispatcher_return_app(dispatcher: PathDispatcher):
def test_dispatcher_users(dispatcher: PathDispatcher):
"""Users special endpoint returns an app"""
"""Users special endpoint returns an app."""
# For now returns the first app, as all apps
# can answer {}/users/login
app = dispatcher({'SCRIPT_NAME:': '/', 'PATH_INFO': '/users/'}, noop)

View File

@ -22,8 +22,7 @@ from tests.conftest import create_user, file
@pytest.mark.usefixtures(conftest.app_context.__name__)
def test_author():
"""
Checks the default created author.
"""Checks the default created author.
Note that the author can be accessed after inserting the row.
"""
@ -341,7 +340,8 @@ def test_price_custom_client(user: UserClient):
def test_ereuse_price():
"""Tests the several ways of creating eReuse Price, emulating
from an AggregateRate and ensuring that the different Range
return correct results."""
return correct results.
"""
# important to check Range.low no returning warranty2
# Range.verylow not returning nothing

View File

@ -13,8 +13,7 @@ from ereuse_devicehub.resources.inventory import Inventory
from ereuse_devicehub.resources.user import User
from tests.conftest import TestConfig
"""
Tests the management of inventories in a multi-inventory environment
"""Tests the management of inventories in a multi-inventory environment
(several Devicehub instances that point at different schemas).
"""

View File

@ -9,8 +9,7 @@ from ereuse_devicehub.resources.enums import ComputerChassis
from ereuse_devicehub.resources.lot.models import Lot, LotDevice
from tests import conftest
"""
In case of error, debug with:
"""In case of error, debug with:
try:
with db.session.begin_nested():
@ -67,7 +66,7 @@ def test_lot_model_children():
def test_lot_modify_patch_endpoint_and_delete(user: UserClient):
"""Creates and modifies lot properties through the endpoint"""
"""Creates and modifies lot properties through the endpoint."""
l, _ = user.post({'name': 'foo', 'description': 'baz'}, res=Lot)
assert l['name'] == 'foo'
assert l['description'] == 'baz'
@ -310,7 +309,8 @@ def test_post_get_lot(user: UserClient):
def test_lot_post_add_children_view_ui_tree_normal(user: UserClient):
"""Tests adding children lots to a lot through the view and
GETting the results."""
GETting the results.
"""
parent, _ = user.post(({'name': 'Parent'}), res=Lot)
child, _ = user.post(({'name': 'Child'}), res=Lot)
parent, _ = user.post({},
@ -347,7 +347,8 @@ def test_lot_post_add_children_view_ui_tree_normal(user: UserClient):
def test_lot_post_add_remove_device_view(app: Devicehub, user: UserClient):
"""Tests adding a device to a lot using POST and
removing it with DELETE."""
removing it with DELETE.
"""
# todo check with components
with app.app_context():
device = Desktop(serial_number='foo',

View File

@ -1,7 +1,6 @@
from decimal import Decimal
from distutils.version import StrictVersion
import math
import pytest
from ereuse_devicehub.client import UserClient
@ -96,8 +95,7 @@ def test_price_from_rate():
def test_when_rate_must_not_compute(user: UserClient):
"""
Test to check if rate is computed in case of should not be calculated:
"""Test to check if rate is computed in case of should not be calculated:
1. Snapshot haven't visual test
2. Snapshot software aren't Workbench
3. Device type are not Computer
@ -138,8 +136,8 @@ def test_multiple_rates(user: UserClient):
ensuring that the tests / benchmarks...
from the first rate do not contaminate the second rate.
This ensures that rates only takes the last version of actions
and components (in case device has new components, for example).
This ensures that rates only takes all the correct actions
and components rates in case device have new tests/benchmarks.
"""
pc = Desktop(chassis=ComputerChassis.Tower)
hdd = HardDrive(size=476940)
@ -172,15 +170,13 @@ def test_multiple_rates(user: UserClient):
assert price1.price == Decimal('92.4001')
hdd = SolidStateDrive(size=476940)
hdd.actions_one.add(BenchmarkDataStorage(read_speed=222, write_speed=169))
cpu = Processor(cores=1, speed=3.0)
cpu.actions_one.add(BenchmarkProcessor(rate=16069.44))
ssd = SolidStateDrive(size=476940)
ssd.actions_one.add(BenchmarkDataStorage(read_speed=222, write_speed=111))
pc.components |= {
hdd,
ssd,
RamModule(size=2048, speed=1067),
RamModule(size=2048, speed=1067),
cpu
}
# Add test visual with functionality and appearance range
@ -190,13 +186,13 @@ def test_multiple_rates(user: UserClient):
rate2, price2 = RateComputer.compute(pc)
assert rate2.data_storage == 4.27
assert rate2.processor == 3.61
assert rate2.ram == 4.12
assert rate2.data_storage == 4.3
assert rate2.processor == 3.78
assert rate2.ram == 3.95
assert rate2.appearance == 0
assert rate2.functionality == -0.5
assert rate2.rating == 3.37
assert rate2.rating == 3.43
assert rate2.price.price == Decimal('67.4001')
assert price2.price == Decimal('68.6001')

View File

@ -1,15 +1,13 @@
"""
This file test all corner cases when compute score v1.0.
"""This file test all corner cases when compute score v1.0.
First test to compute rate for every component in isolation.
First test to compute rate for every component in isolation
todo rewrite some corner cases using range(min,max) characteristics
in devices/schemas
Components in Score v1:
-DataStorage
-RamModule
-Processor
Then some test compute rate with all components that use the a1lgorithm
Then some test compute rate with all components that use the algorithm
Excluded cases in tests
@ -31,9 +29,8 @@ from tests import conftest
def test_rate_data_storage_rate():
"""
Test to check if compute data storage rate have same value than previous score version;
id = pc_1193, pc_1201, pc_79, pc_798
"""Test to check if compute data storage rate have same value than
previous score version.
"""
hdd_1969 = HardDrive(size=476940)
@ -67,10 +64,8 @@ def test_rate_data_storage_rate():
def test_rate_data_storage_size_is_null():
"""
Test where input DataStorage.size = NULL, BenchmarkDataStorage.read_speed = 0,
"""Test where input DataStorage.size = NULL, BenchmarkDataStorage.read_speed = 0,
BenchmarkDataStorage.write_speed = 0 is like no DataStorage has been detected;
id = pc_2992
"""
hdd_null = HardDrive(size=None)
@ -81,9 +76,7 @@ def test_rate_data_storage_size_is_null():
def test_rate_no_data_storage():
"""
Test without data storage devices
"""
"""Test without data storage devices."""
hdd_null = HardDrive()
hdd_null.actions_one.add(BenchmarkDataStorage(read_speed=0, write_speed=0))
@ -92,9 +85,8 @@ def test_rate_no_data_storage():
def test_rate_ram_rate():
"""
Test to check if compute ram rate have same value than previous score version
only with 1 RamModule; id = pc_1201
"""Test to check if compute ram rate have same value than previous
score version only with 1 RamModule.
"""
ram1 = RamModule(size=2048, speed=1333)
@ -105,9 +97,8 @@ def test_rate_ram_rate():
def test_rate_ram_rate_2modules():
"""
Test to check if compute ram rate have same value than previous score version
with 2 RamModule; id = pc_1193
"""Test to check if compute ram rate have same value than previous
score version with 2 RamModule.
"""
ram1 = RamModule(size=4096, speed=1600)
@ -119,9 +110,8 @@ def test_rate_ram_rate_2modules():
def test_rate_ram_rate_4modules():
"""
Test to check if compute ram rate have same value than previous score version
with 2 RamModule; id = pc_79
"""Test to check if compute ram rate have same value than previous
score version with 2 RamModule.
"""
ram1 = RamModule(size=512, speed=667)
@ -135,8 +125,8 @@ def test_rate_ram_rate_4modules():
def test_rate_ram_module_size_is_0():
"""
Test where input data RamModule.size = 0; is like no RamModule has been detected; id = pc_798
"""Test where input data RamModule.size = 0; is like no RamModule
has been detected.
"""
ram0 = RamModule(size=0, speed=888)
@ -146,10 +136,7 @@ def test_rate_ram_module_size_is_0():
def test_rate_ram_speed_is_null():
"""
Test where RamModule.speed is NULL (not detected) but has size.
Pc ID = 795(1542), 745(1535), 804(1549)
"""
"""Test where RamModule.speed is NULL (not detected) but has size."""
ram0 = RamModule(size=2048, speed=None)
@ -165,9 +152,7 @@ def test_rate_ram_speed_is_null():
def test_rate_no_ram_module():
"""
Test without RamModule
"""
"""Test without RamModule."""
ram0 = RamModule()
ram_rate = RamRate().compute([ram0])
@ -175,9 +160,8 @@ def test_rate_no_ram_module():
def test_rate_processor_rate():
"""
Test to check if compute processor rate have same value than previous score version
only with 1 core; id = 79
"""Test to check if compute processor rate have same value than previous
score version only with 1 core.
"""
cpu = Processor(cores=1, speed=1.6)
@ -190,9 +174,8 @@ def test_rate_processor_rate():
def test_rate_processor_rate_2cores():
"""
Test to check if compute processor rate have same value than previous score version
with 2 cores; id = pc_1193, pc_1201
"""Test to check if compute processor rate have same value than previous
score version with 2 cores.
"""
cpu = Processor(cores=2, speed=3.4)
@ -211,13 +194,8 @@ def test_rate_processor_rate_2cores():
assert math.isclose(processor_rate, 3.93, rel_tol=0.002)
# TODO JN if delete processor default score for benchmark_cpu
def test_rate_processor_with_null_cores():
"""
Test with processor device have null number of cores
"""
"""Test with processor device have null number of cores."""
cpu = Processor(cores=None, speed=3.3)
cpu.actions_one.add(BenchmarkProcessor(rate=0))
@ -227,9 +205,7 @@ def test_rate_processor_with_null_cores():
def test_rate_processor_with_null_speed():
"""
Test with processor device have null speed value
"""
"""Test with processor device have null speed value."""
cpu = Processor(cores=1, speed=None)
cpu.actions_one.add(BenchmarkProcessor(rate=0))
@ -238,13 +214,9 @@ def test_rate_processor_with_null_speed():
assert math.isclose(processor_rate, 1.06, rel_tol=0.001)
# TODO JN add price asserts in rate computers??
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_rate_computer_1193():
"""
Test rate computer characteristics:
"""Test rate computer characteristics:
- 2 module ram
- processor with 2 cores
@ -297,8 +269,7 @@ def test_rate_computer_1193():
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_rate_computer_1201():
"""
Test rate computer characteristics:
"""Test rate computer characteristics:
- only 1 module ram
- processor 2 cores
@ -349,8 +320,7 @@ def test_rate_computer_1201():
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_rate_computer_multiple_ram_module():
"""
Test rate computer characteristics:
"""Test rate computer characteristics:
- only 1 module ram
- processor 2 cores
@ -408,8 +378,7 @@ def test_rate_computer_multiple_ram_module():
@pytest.mark.usefixtures(conftest.auth_app_context.__name__)
def test_rate_computer_one_ram_module():
"""
Test rate computer characteristics:
"""Test rate computer characteristics:
- only 1 module ram
- processor 2 cores
@ -463,4 +432,5 @@ def test_rate_computer_one_ram_module():
@pytest.mark.xfail(reason='Data Storage rate actually requires a DSSBenchmark')
def test_rate_computer_with_data_storage_without_benchmark():
"""For example if the data storage was introduced manually
or comes from an old version without benchmark."""
or comes from an old version without benchmark.
"""

View File

@ -3,8 +3,6 @@ from datetime import datetime
from io import StringIO
from pathlib import Path
import pytest
from ereuse_devicehub.client import UserClient
from ereuse_devicehub.resources.action.models import Snapshot
from ereuse_devicehub.resources.documents import documents
@ -12,9 +10,7 @@ from tests.conftest import file
def test_export_basic_snapshot(user: UserClient):
"""
Test export device information in a csv file
"""
"""Test export device information in a csv file."""
snapshot, _ = user.post(file('basic.snapshot'), res=Snapshot)
csv_str, _ = user.get(res=documents.DocumentDef.t,
item='devices/',
@ -41,9 +37,7 @@ def test_export_basic_snapshot(user: UserClient):
def test_export_full_snapshot(user: UserClient):
"""
Test a export device with all information and a lot of components
"""
"""Test a export device with all information and a lot of components."""
snapshot, _ = user.post(file('real-eee-1001pxd.snapshot.11'), res=Snapshot)
csv_str, _ = user.get(res=documents.DocumentDef.t,
item='devices/',
@ -71,8 +65,8 @@ def test_export_full_snapshot(user: UserClient):
def test_export_empty(user: UserClient):
"""
Test to check works correctly exporting csv without any information (no snapshot)
"""Test to check works correctly exporting csv without any information,
export a placeholder device.
"""
csv_str, _ = user.get(res=documents.DocumentDef.t,
accept='text/csv',
@ -85,9 +79,7 @@ def test_export_empty(user: UserClient):
def test_export_computer_monitor(user: UserClient):
"""
Test a export device type computer monitor
"""
"""Test a export device type computer monitor."""
snapshot, _ = user.post(file('computer-monitor.snapshot'), res=Snapshot)
csv_str, _ = user.get(res=documents.DocumentDef.t,
item='devices/',
@ -111,9 +103,7 @@ def test_export_computer_monitor(user: UserClient):
def test_export_keyboard(user: UserClient):
"""
Test a export device type keyboard
"""
"""Test a export device type keyboard."""
snapshot, _ = user.post(file('keyboard.snapshot'), res=Snapshot)
csv_str, _ = user.get(res=documents.DocumentDef.t,
item='devices/',
@ -136,8 +126,7 @@ def test_export_keyboard(user: UserClient):
def test_export_multiple_different_devices(user: UserClient):
"""
Test function 'Export' of multiple different device types (like
"""Test function 'Export' of multiple different device types (like
computers, keyboards, monitors, etc..)
"""
# Open fixture csv and transform to list

View File

@ -26,8 +26,7 @@ from tests.conftest import file
@pytest.mark.usefixtures('auth_app_context')
def test_snapshot_model():
"""
Tests creating a Snapshot with its relationships ensuring correct
"""Tests creating a Snapshot with its relationships ensuring correct
DB mapping.
"""
device = m.Desktop(serial_number='a1', chassis=ComputerChassis.Tower)
@ -62,8 +61,7 @@ def test_snapshot_schema(app: Devicehub):
def test_snapshot_post(user: UserClient):
"""
Tests the post snapshot endpoint (validation, etc), data correctness,
"""Tests the post snapshot endpoint (validation, etc), data correctness,
and relationship correctness.
"""
# TODO add all action_types to check, how to add correctly??
@ -98,8 +96,7 @@ def test_snapshot_post(user: UserClient):
def test_snapshot_component_add_remove(user: UserClient):
"""
Tests adding and removing components and some don't generate HID.
"""Tests adding and removing components and some don't generate HID.
All computers generate HID.
"""
@ -212,8 +209,7 @@ def test_snapshot_component_add_remove(user: UserClient):
def _test_snapshot_computer_no_hid(user: UserClient):
"""
Tests inserting a computer that doesn't generate a HID, neither
"""Tests inserting a computer that doesn't generate a HID, neither
some of its components.
"""
# PC with 2 components. PC doesn't have HID and neither 1st component
@ -384,18 +380,14 @@ def test_test_data_storage(user: UserClient):
@pytest.mark.xfail(reason='Not implemented yet, new rate is need it')
def test_snapshot_computer_monitor(user: UserClient):
"""
Tests that a snapshot of computer monitor device create correctly.
"""
"""Tests that a snapshot of computer monitor device create correctly."""
s = file('computer-monitor.snapshot')
snapshot_and_check(user, s, action_types=('RateMonitor',))
@pytest.mark.xfail(reason='Not implemented yet, new rate is need it')
def test_snapshot_mobile_smartphone_imei_manual_rate(user: UserClient):
"""
Tests that a snapshot of smartphone device is creat correctly.
"""
"""Tests that a snapshot of smartphone device is creat correctly."""
s = file('smartphone.snapshot')
snapshot = snapshot_and_check(user, s, action_types=('VisualTest',))
mobile, _ = user.get(res=m.Device, item=snapshot['device']['id'])
@ -404,24 +396,21 @@ def test_snapshot_mobile_smartphone_imei_manual_rate(user: UserClient):
@pytest.mark.xfail(reason='Test not developed')
def test_snapshot_components_none():
"""
Tests that a snapshot without components does not
remove them from the computer.
"""Tests that a snapshot without components does not remove them
from the computer.
"""
# TODO JN is really necessary in which cases??
@pytest.mark.xfail(reason='Test not developed')
def test_snapshot_components_empty():
"""
Tests that a snapshot whose components are an empty list remove
"""Tests that a snapshot whose components are an empty list remove
all its components.
"""
def assert_similar_device(device1: dict, device2: dict):
"""
Like :class:`ereuse_devicehub.resources.device.models.Device.
"""Like :class:`ereuse_devicehub.resources.device.models.Device.
is_similar()` but adapted for testing.
"""
assert isinstance(device1, dict) and device1
@ -431,9 +420,8 @@ def assert_similar_device(device1: dict, device2: dict):
def assert_similar_components(components1: List[dict], components2: List[dict]):
"""
Asserts that the components in components1 are
similar than the components in components2.
"""Asserts that the components in components1 are similar than
the components in components2.
"""
assert len(components1) == len(components2)
key = itemgetter('serialNumber')
@ -447,8 +435,7 @@ def snapshot_and_check(user: UserClient,
input_snapshot: dict,
action_types: Tuple[str, ...] = tuple(),
perform_second_snapshot=True) -> dict:
"""
Performs a Snapshot and then checks if the result is ok:
"""Performs a Snapshot and then checks if the result is ok:
- There have been performed the types of actions and in the same
order as described in the passed-in ``action_types``.

View File

@ -80,8 +80,7 @@ def test_tag_post(app: Devicehub, user: UserClient):
def test_tag_post_etag(user: UserClient):
"""
Ensures users cannot create eReuse.org tags through POST;
"""Ensures users cannot create eReuse.org tags through POST;
only terminal.
"""
user.post({'id': 'FO-123456'}, res=Tag, status=CannotCreateETag)
@ -121,8 +120,7 @@ def test_tag_get_device_from_tag_endpoint_no_tag(user: UserClient):
def test_tag_get_device_from_tag_endpoint_multiple_tags(app: Devicehub, user: UserClient):
"""
As above, but when there are two tags with the same ID, the
"""As above, but when there are two tags with the same ID, the
system should not return any of both (to be deterministic) so
it should raise an exception.
"""

View File

@ -18,8 +18,7 @@ from tests.conftest import app_context, create_user
@pytest.mark.usefixtures(app_context.__name__)
def test_create_user_method_with_agent(app: Devicehub):
"""
Tests creating an user through the main method.
"""Tests creating an user through the main method.
This method checks that the token is correct, too.
"""
@ -63,8 +62,7 @@ def test_hash_password():
def test_login_success(client: Client, app: Devicehub):
"""
Tests successfully performing login.
"""Tests successfully performing login.
This checks that:
- User is returned.

View File

@ -1,10 +1,8 @@
"""
Tests that emulates the behaviour of a WorkbenchServer.
"""
"""Tests that emulates the behaviour of a WorkbenchServer."""
import json
import math
import pathlib
import math
import pytest
from ereuse_devicehub.client import UserClient
@ -17,8 +15,7 @@ from tests.conftest import file
def test_workbench_server_condensed(user: UserClient):
"""
As :def:`.test_workbench_server_phases` but all the actions
"""As :def:`.test_workbench_server_phases` but all the actions
condensed in only one big ``Snapshot`` file, as described
in the docs.
"""
@ -72,8 +69,7 @@ def test_workbench_server_condensed(user: UserClient):
@pytest.mark.xfail(reason='Functionality not yet developed.')
def test_workbench_server_phases(user: UserClient):
"""
Tests the phases described in the docs section `Snapshots from
"""Tests the phases described in the docs section `Snapshots from
Workbench <http://devicehub.ereuse.org/
actions.html#snapshots-from-workbench>`_.
"""
@ -170,8 +166,7 @@ def test_real_toshiba_11(user: UserClient):
def test_snapshot_real_eee_1001pxd_with_rate(user: UserClient):
"""
Checks the values of the device, components,
"""Checks the values of the device, components,
actions and their relationships of a real pc.
"""
s = file('real-eee-1001pxd.snapshot.11')