Add sphinx extension dhclass; generate better API docs; remove cache for lots

This commit is contained in:
Xavier Bustamante Talavera 2019-02-03 17:12:53 +01:00
parent eabf6aad54
commit 198a89a0b1
17 changed files with 609 additions and 382 deletions

View file

@ -1,6 +1,9 @@
Actions and states
##################
Actions
*******
Actions are events performed to devices, changing their **state**.
Actions can have attributes defining
**where** it happened, **who** performed them, **when**, etc.
@ -8,13 +11,6 @@ Actions are stored in a log for each device. An exemplifying action
can be ``Repair``, which dictates that a device has been repaired,
after this action, the device is in the ``repaired`` state.
Actions and states affect devices in different ways or **dimensions**.
For example, ``Repair`` affects the **physical** dimension of a device,
and ``Sell`` the **political** dimension of a device. A device
can be in several states at the same time, one per dimension; ie. a
device can be ``repaired`` (physical) and ``reserved`` (political),
but not ``repaired`` and ``disposed`` at the same time.
Devicehub actions inherit from `schema actions
<http://schema.org/Action>`_, are written in Pascal case and using
a verb in infinitive. Some verbs represent the willingness or
@ -23,240 +19,49 @@ is going to be / must be repaired, whereas ``Repair`` states
that the reparation happened. The former actions have the preposition
*To* prefixing the verb.
In the following section we define the actions and states.
To see how to perform actions to the Devicehub API head
to the `Swagger docs
<https://app.swaggerhub.com/apis/ereuse/devicehub/0.2>`_.
.. toctree::
:maxdepth: 4
actions
.. uml:: actions.puml
Actions and states affect devices in different ways or **dimensions**.
For example, ``Repair`` affects the **physical** dimension of a device,
and ``Sell`` the **political** dimension of a device. A device
can be in several states at the same time, one per dimension; ie. a
device can be ``repaired`` (physical) and ``reserved`` (political),
but not ``repaired`` and ``disposed`` at the same time:
Physical Actions
****************
The following actions describe and react on the
:class:`ereuse_devicehub.resources.device.states.Physical` condition
of the devices.
- Physical actions: The following actions describe and react on the
Physical condition of the devices.
ToPrepare and Prepare
==================
Prepare
-------
.. autoclass:: ereuse_devicehub.resources.event.models.Prepare
ToPrepare
---------
.. autoclass:: ereuse_devicehub.resources.event.models.ToPrepare
- ToPrepare and prepare.
- ToRepair, Repair
- ReadyToUse
- Live
- DisposeWaste, Recover
ToRepair, Repair
================
Repair
------
.. autoclass:: ereuse_devicehub.resources.event.models.Repair
ToRepair
--------
.. autoclass:: ereuse_devicehub.resources.event.models.ToRepair
- Association actions: Actions that change the associations users have with devices;
ie. the **owners**, **usufructuarees**, **reservees**,
and **physical possessors**.
ReadyToUse
==========
.. autoclass:: ereuse_devicehub.resources.event.models.ReadyToUse
- Trade
- Transfer
- Organize
Live
====
.. autoclass:: ereuse_devicehub.resources.event.models.Live
- Internal state actions: Actions providing metadata about devices that don't usually change
their state.
DisposeWaste, Recover
=====================
``RecyclingCenter`` users have two extra special events:
- ``DisposeWaste``: The device has been disposed in an unspecified
manner.
- ``Recover``: The device has been scrapped and its materials have
been recovered under a new product.
See `ToDisposeProduct, DisposeProduct`_.
.. todo:: Events not developed yet.
Association actions
*******************
Actions that change the associations users have with devices;
ie. the **owners**, **usufructuarees**, **reservees**,
and **physical possessors**.
There are three sub-dimensions: **trade**, **transfer**,
and **organize** actions.
.. uml:: association-events.puml
Trade
=====
.. todo Not fully developed.
.. autoclass:: ereuse_devicehub.resources.event.models.Trade
Sell
----
.. autoclass:: ereuse_devicehub.resources.event.models.Sell
Donate
------
.. autoclass:: ereuse_devicehub.resources.event.models.Donate
Rent
----
.. autoclass:: ereuse_devicehub.resources.event.models.Rent
CancelTrade
-----------
.. autoclass:: ereuse_devicehub.resources.event.models.CancelTrade
ToDisposeProduct, DisposeProduct
--------------------------------
.. autoclass:: ereuse_devicehub.resources.event.models.DisposeProduct
.. autoclass:: ereuse_devicehub.resources.event.models.ToDisposeProduct
Transfer actions
================
The act of transferring/moving devices from one place to another.
Receive
-------
.. autoclass:: ereuse_devicehub.resources.event.models.Receive
.. autoclass:: ereuse_devicehub.resources.enums.ReceiverRole
:members:
:undoc-members:
.. autoattribute:: ereuse_devicehub.resources.device.models.Device.physical_possessor
Organize actions
================
.. autoclass:: ereuse_devicehub.resources.event.models.Organize
Reserve, CancelReservation
-------------------------
Not fully developed.
.. autoclass:: ereuse_devicehub.resources.event.models.Reserve
.. autoclass:: ereuse_devicehub.resources.event.models.CancelReservation
Assign, Accept, Reject
----------------------
Not developed.
``Assign`` allocates devices to an user. The purpose or meaning
of the association is defined by the users.
``Accept`` and ``Reject`` allow users to accept and reject the
assignments.
.. todo:: shall we add ``Deassign`` or make ``Assign``
always define all active users?
Assign won't be developed until further notice.
Internal state actions
**********************
Actions providing metadata about devices that don't usually change
their state.
Snapshot
========
.. autoclass:: ereuse_devicehub.resources.event.models.Snapshot
- Snapshot
- Add, remove
- Erase
- Install
- Test
- Benchmark
- Rate
- Price
Add, Remove
===========
.. autoclass:: ereuse_devicehub.resources.event.models.Add
.. autoclass:: ereuse_devicehub.resources.event.models.Remove
The following index has all the actions (please note we are moving from calling them
``Event`` to call them ``Action``):
Erase
=====
.. autoclass:: ereuse_devicehub.resources.event.models.EraseBasic
.. autoclass:: ereuse_devicehub.resources.event.models.EraseSectors
.. autoclass:: ereuse_devicehub.resources.enums.ErasureStandards
:members:
.. autoclass:: ereuse_devicehub.resources.event.models.ErasePhysical
.. autoclass:: ereuse_devicehub.resources.enums.PhysicalErasureMethod
:members:
:undoc-members:
Install
=======
.. autoclass:: ereuse_devicehub.resources.event.models.Install
Test
====
.. autoclass:: ereuse_devicehub.resources.event.models.Test
TestDataStorage
---------------
.. autoclass:: ereuse_devicehub.resources.event.models.TestDataStorage
StressTest
----------
.. autoclass:: ereuse_devicehub.resources.event.models.StressTest
Benchmark
=========
.. autoclass:: ereuse_devicehub.resources.event.models.Benchmark
BenchmarkDataStorage
--------------------
.. autoclass:: ereuse_devicehub.resources.event.models.BenchmarkDataStorage
BenchmarkWithRate
-----------------
.. autoclass:: ereuse_devicehub.resources.event.models.BenchmarkWithRate
BenchmarkProcessor
------------------
.. autoclass:: ereuse_devicehub.resources.event.models.BenchmarkProcessor
BenchmarkProcessorSysbench
--------------------------
.. autoclass:: ereuse_devicehub.resources.event.models.BenchmarkProcessorSysbench
BenchmarkRamSysbench
--------------------
.. autoclass:: ereuse_devicehub.resources.event.models.BenchmarkRamSysbench
Rate
====
.. autoclass:: ereuse_devicehub.resources.event.models.Rate
The following are the values the appearance, performance, and
functionality grade can have:
.. autoclass:: ereuse_devicehub.resources.enums.AppearanceRange
:members:
:undoc-members:
.. autoclass:: ereuse_devicehub.resources.enums.FunctionalityRange
:members:
:undoc-members:
.. autoclass:: ereuse_devicehub.resources.enums.RatingRange
Price
=====
.. autoclass:: ereuse_devicehub.resources.event.models.Price
Migrate
=======
Not done.
.. autoclass:: ereuse_devicehub.resources.event.models.Migrate
Locate
======
todo
.. todo !!
.. dhlist::
:module: ereuse_devicehub.resources.event.schemas
States
@ -266,8 +71,4 @@ States
.. uml:: states.puml
.. autoclass:: ereuse_devicehub.resources.device.states.Trading
:members:
:undoc-members:
.. autoclass:: ereuse_devicehub.resources.device.states.Physical
:members:
:undoc-members:

70
docs/api.rst Normal file
View file

@ -0,0 +1,70 @@
Using the API
#############
Devicehub is a REST API on the web that partially extends Schema.org's
ontology and it is formatted in JSON.
The main resource are devices. However, you do not perform operations
directly against them (there is no ``POST /device``),
as you use an Action / Event to do so (you only ``GET /devices``).
For example, to upload information of devices with tests, erasures, etcetera, use
the action/event ``POST /snapshot`` (:ref:`devices-snapshot`).
Login
*****
To use the API, you need first to log in with an existing account from the DeviceHub.
Perform ``POST /users/login/`` with the email and password fields filled::
POST /users/login/
Content-Type: application/json
Accept: application/json
{
"email": "user@dhub.com",
"password: "1234"
}
Upon success, you are answered with the account object, containing a Token field::
{
"id": "...",
"token: "A base 64 codified token",
"type": "User",
"inventories": [{"type": "Inventory", id: "db1", ...}, ...],
...
}
From this moment, any other following operation against
the API requires the following HTTP Header:
``Authorization: Basic token``. This is, the word **Basic**
followed with a **space** and then the **token**,
obtained from the account object above, **exactly as it is**.
.. _authenticate-requests:
Authenticate requests
---------------------
To explain how to operate with resources like events or devices, we
use one as an example: obtaining devices. The template of
a request is::
GET <inventory>/devices/
Accept: application/json
Authorization: Basic <token>
And an example is::
GET acme/devices/
Accept: application/json
Authorization: Basic myTokenInBase64
Let's go through the variables:
- ``<inventory>`` is the name of the inventory where you operate.
You get this value from the ``User`` object returned from the login.
The ``inventories`` field contains a set of databases the account
can operate with, being the first inventory the default one.
- ``<token>`` is the token of the account.
See :ref:`devices:devices` for more information on how to query
devices.

View file

@ -18,6 +18,19 @@
# -- Project information -----------------------------------------------------
import importlib
import inspect
from typing import Union
from docutils.parsers.rst import Directive, directives
from docutils.statemachine import StringList, string2lines
from marshmallow.fields import DateTime, Field
from marshmallow.schema import SchemaMeta
from teal.enums import Country, Currency, Layouts, Subdivision
from teal.marshmallow import EnumField
from ereuse_devicehub.marshmallow import NestedOn
from ereuse_devicehub.resources.schemas import Thing
project = 'Devicehub'
copyright = '2018, eReuse.org team'
@ -176,3 +189,123 @@ html_favicon = 'img/favicon.ico'
# autosectionlabel
autosectionlabel_prefix_document = True
autodoc_member_order = 'bysource'
import docutils.nodes as n
class DhlistDirective(Directive):
"""Generates documentation from Devicehub Schema.
This requires :py:class:`ereuse_devicehub.resources.schemas.SchemaMeta`.
You will find in that module more information.
"""
has_content = False
# Definition of passed-in options
option_spec = {'module': directives.unchanged}
def _import(self, module):
for obj in vars(module).values():
if inspect.isclass(obj):
if isinstance(obj, SchemaMeta) and hasattr(obj, '_base_class'):
yield obj
def run(self):
env = self.state.document.settings.env
module = importlib.import_module(self.options['module'])
things = tuple(self._import(module))
sections = []
sections.append(self.links(things)) # Make index
for thng in things: # type: Thing
# Generate a section for each class, with a title,
# fields description and a paragraph
section = n.section(ids=[self._id(thng)])
section += n.title(thng.__name__, thng.__name__)
section += self.parse('*Extends {}*'.format(thng._base_class))
if thng.__doc__:
section += self.parse(thng.__doc__)
fields = n.field_list()
for key, f in thng._own:
name = n.field_name(text=f.data_key or key)
body = [
self.parse('{} {}'.format(self.type(f), f.metadata.get('description', '')))
]
if isinstance(f, EnumField):
body.append(self._parse_enum_field(f))
attrs = n.field_list()
if f.dump_only:
attrs += self.field('Submit', 'No.')
if f.required:
attrs += self.field('Required', f.required)
fields += n.field('', name, n.field_body('', *body, attrs))
section += fields
sections.append(section)
return sections
def _parse_enum_field(self, f):
from ereuse_devicehub.resources.device import states
if issubclass(f.enum, (Subdivision, Currency, Country, Layouts, states.State)):
return self.parse(f.enum.__doc__)
else:
enum_fields = n.field_list()
for el in f.enum:
enum_fields += self.field(el.name, el.value)
return enum_fields
def field(self, name: str, body: Union[str, bool]):
"""Generates a field node with a name and a paragraph body."""
if isinstance(body, bool):
body = 'Yes.' if body else 'No.'
body = str(body) if body else ''
return n.field('', n.field_name(text=name), n.field_body('', self.parse(body)))
def type(self, field: Field):
"""Parses the type field."""
if isinstance(field, NestedOn):
t = ''
if field.many:
t = 'List of '
t = t + str(field.schema.t)
elif isinstance(field, EnumField):
t = field.enum.__name__
elif isinstance(field, DateTime):
t = 'Date time (ISO 8601 with timezone)'
else:
t = field.__class__.__name__
if 'str' in t.lower():
t = 'Text'
if 'unit' in field.metadata:
t = t + ' ({})'.format(field.metadata['unit'])
return t + '.'
def links(self, things, parent='Schema'):
"""Generates an index of things with inheritance awareness."""
l = n.bullet_list('')
for child in (c for c in things if c._base_class == parent):
ref = n.reference(text=child.__name__)
ref['refuri'] = '#{}'.format(self._id(child))
p = n.paragraph()
p += ref
l += n.list_item('', p)
sub_list = self.links(things, parent=child.__name__)
if sub_list:
l += sub_list
return l
def _id(self, thing):
"""Generate an id to use as html anchors."""
return n.make_id('dh-{}'.format(thing.__name__))
def parse(self, text) -> n.container:
"""Parses text possibly containing ReST stuff and adds it in
a node."""
p = n.container('')
self.state.nested_parse(StringList(string2lines(inspect.cleandoc(text))), 0, p)
return p
# return publish_doctree(text).children
def setup(app):
app.add_directive('dhlist', DhlistDirective)
return {'version': '0.1'}

View file

@ -37,19 +37,14 @@ Result
******
The result is a JSON object with the following fields:
- **devices**: A list of devices.
- **groups**: A list of groups.
- **widgets**: A dictionary of widgets.
- **pagination**: Pagination information:
- **items**: A list of devices.
- **pagination**:
- **page**: The page you requested in the ``page`` param of the query,
or ``1``.
- **perPage**: How many devices are in every page, fixed to ``30``.
- **total**: How many total devices passed the filters.
- **next**: The number of the next page, if any.
- **last**: The number of the last page, if any.
Models
******
.. automodule:: ereuse_devicehub.resources.device.models
:members:
:member-order: bysource
.. dhlist::
:module: ereuse_devicehub.resources.device.schemas

View file

@ -31,10 +31,9 @@ Devicehub is built with `Teal <https://github.com/bustawin/teal>`_ and
.. toctree::
:maxdepth: 2
processes
actions
agents
api
devices
actions
tags
lots

View file

@ -27,11 +27,11 @@ class Dummy:
)
"""Tags to create."""
ET = (
('A0000000000001', 'DT-AAAAA'),
('A0000000000002', 'DT-BBBBB'),
('A0000000000003', 'DT-CCCCC'),
('04970DA2A15984', 'DT-BRRAB'),
('04e4bc5af95980', 'DT-XXXXX')
('DT-AAAAA', 'A0000000000001'),
('DT-BBBBB', 'A0000000000002'),
('DT-CCCCC', 'A0000000000003'),
('DT-BRRAB', '04970DA2A15984'),
('DT-XXXXX', '04e4bc5af95980')
)
"""eTags to create."""
ORG = 'eReuse.org CAT', '-t', 'G-60437761', '-c', 'ES'

View file

@ -29,44 +29,75 @@ from ereuse_devicehub.resources.models import STR_SM_SIZE, Thing
class Device(Thing):
"""
Base class for any type of physical object that can be identified.
"""Base class for any type of physical object that can be identified.
Device partly extends `Schema's IndividualProduct <https
://schema.org/IndividualProduct>`_, adapting it to our
use case.
A device requires an identification method, ideally a serial number,
although it can be identified only with tags too. More ideally
both methods are used.
Devices can contain ``Components``, which are just a type of device
(it is a recursive relationship).
"""
EVENT_SORT_KEY = attrgetter('created')
id = Column(BigInteger, Sequence('device_seq'), primary_key=True)
id.comment = """
The identifier of the device for this database.
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, index=True)
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.
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*.
The HID is the result of joining the type of device, S/N,
manufacturer name, and model. Devices that do not have one
of these fields cannot generate HID, thus not guaranteeing
global uniqueness.
"""
model = Column(Unicode(), check_lower('model'))
model.comment = """The model or brand of the device in lower case.
Devices usually report one of both (model or brand). This value
must be consistent through time.
"""
manufacturer = Column(Unicode(), check_lower('manufacturer'))
manufacturer.comment = """The normalized name of the manufacturer
in lower case.
Although as of now Devicehub does not enforce normalization,
users can choose a list of normalized manufacturer names
from the own ``/manufacturers`` REST endpoint.
"""
serial_number = Column(Unicode(), check_lower('serial_number'))
serial_number.comment = """The serial number of the device in lower case."""
weight = Column(Float(decimal_return_scale=3), check_range('weight', 0.1, 5))
weight.comment = """
The weight of the device in Kgm.
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.
The width of the device.
"""
height = Column(Float(decimal_return_scale=3), check_range('height', 0.1, 5))
height.comment = """
The height of the device in meters.
The height of the device.
"""
depth = Column(Float(decimal_return_scale=3), check_range('depth', 0.1, 5))
depth.comment = """
The depth of the device in meters.
The depth of the device.
"""
color = Column(ColorType)
color.comment = """The predominant color of the device."""
production_date = Column(db.TIMESTAMP(timezone=True))
production_date.comment = """The date of production of the item."""
production_date.comment = """The date of production of the device."""
_NON_PHYSICAL_PROPS = {
'id',
@ -91,11 +122,13 @@ class Device(Thing):
@property
def events(self) -> list:
"""
All the events where the device participated, including
1) events performed directly to the device, 2) events performed
to a component, and 3) events performed to a parent device.
All the events where the device participated, including:
Events are returned by ascending creation time.
1. Events performed directly to the device.
2. Events performed to a component.
3. Events performed to a parent device.
Events are returned by ascending ``created`` time.
"""
return sorted(chain(self.events_multiple, self.events_one), key=self.EVENT_SORT_KEY)
@ -198,7 +231,7 @@ class Device(Thing):
device is working if the list is empty.
This property returns, for the last test performed of each type,
the one with the worst severity of them, or `None` if no
the one with the worst ``severity`` of them, or ``None`` if no
test has been executed.
"""
from ereuse_devicehub.resources.event.models import Test
@ -292,8 +325,18 @@ class DisplayMixin:
class Computer(Device):
"""A chassis with components inside that can be processed
automatically with Workbench Computer.
Computer is broadly extended by ``Desktop``, ``Laptop``, and
``Server``. The property ``chassis`` defines it more granularly.
"""
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
chassis = Column(DBEnum(ComputerChassis), nullable=False)
chassis.comment = """The physical form of the computer.
It is a subset of the Linux definition of DMI / DMI decode.
"""
def __init__(self, chassis, **kwargs) -> None:
chassis = ComputerChassis(chassis)
@ -342,7 +385,7 @@ class Computer(Device):
@property
def privacy(self):
"""Returns the privacy of all DataStorage components when
"""Returns the privacy of all ``DataStorage`` components when
it is not None.
"""
return set(
@ -395,6 +438,8 @@ class Projector(Monitor):
class Mobile(Device):
"""A mobile device consisting of smartphones, tablets, and cellphones."""
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
imei = Column(BigInteger)
imei.comment = """
@ -432,6 +477,7 @@ class Cellphone(Mobile):
class Component(Device):
"""A device that can be inside another device."""
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
parent_id = Column(BigInteger, ForeignKey(Computer.id), index=True)
@ -481,6 +527,7 @@ class GraphicCard(JoinedComponentTableMixin, Component):
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.
@ -548,14 +595,21 @@ class NetworkAdapter(JoinedComponentTableMixin, NetworkMixin, Component):
class Processor(JoinedComponentTableMixin, Component):
"""The CPU."""
speed = Column(Float, check_range('speed', 0.1, 15))
speed.comment = """The regular CPU speed."""
cores = Column(SmallInteger, check_range('cores', 1, 10))
cores.comment = """The number of regular cores."""
threads = Column(SmallInteger, check_range('threads', 1, 20))
threads.comment = """The number of threads per core."""
address = Column(SmallInteger, check_range('address', 8, 256))
address.comment = """The address of the CPU: 8, 16, 32, 64, 128 or 256 bits."""
class RamModule(JoinedComponentTableMixin, Component):
"""A stick of RAM."""
size = Column(SmallInteger, check_range('size', min=128, max=17000))
size.comment = """The capacity of the RAM stick."""
speed = Column(SmallInteger, check_range('speed', min=100, max=10000))
interface = Column(DBEnum(RamInterface))
format = Column(DBEnum(RamFormat))
@ -568,14 +622,15 @@ class SoundCard(JoinedComponentTableMixin, Component):
class Display(JoinedComponentTableMixin, DisplayMixin, Component):
"""
The display of a device. This is used in all devices that have
displays but that it is not their main treat, like laptops,
mobiles, smart-watches, and so on; excluding then ComputerMonitor
and Television Set.
displays but that it is not their main part, like laptops,
mobiles, smart-watches, and so on; excluding ``ComputerMonitor``
and ``TelevisionSet``.
"""
pass
class ComputerAccessory(Device):
"""Computer peripherals and similar accessories."""
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
pass
@ -597,6 +652,7 @@ class MemoryCardReader(ComputerAccessory):
class Networking(NetworkMixin, Device):
"""Routers, switches, hubs..."""
id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
@ -641,6 +697,7 @@ class Microphone(Sound):
class Video(Device):
"""Devices related to video treatment."""
pass
@ -653,6 +710,7 @@ class Videoconference(Video):
class Cooking(Device):
"""Cooking devices."""
pass
@ -661,6 +719,11 @@ class Mixer(Cooking):
class Manufacturer(db.Model):
"""The normalized information about a manufacturer.
Ideally users should use the names from this list when submitting
devices.
"""
__table_args__ = {'schema': 'common'}
CSV_DELIMITER = csv.get_dialect('excel').delimiter
@ -668,8 +731,11 @@ class Manufacturer(db.Model):
primary_key=True,
# from https://niallburkley.com/blog/index-columns-for-like-in-postgres/
index=db.Index('name', text('name gin_trgm_ops'), postgresql_using='gin'))
name.comment = """The normalized name of the manufacturer."""
url = db.Column(URL(), unique=True)
url.comment = """An URL to a page describing the manufacturer."""
logo = db.Column(URL())
logo.comment = """An URL pointing to the logo of the manufacturer."""
@classmethod
def add_all_to_session(cls, session: db.Session):

View file

@ -287,11 +287,13 @@ class Processor(Component):
speed = ... # type: Column
cores = ... # type: Column
address = ... # type: Column
threads = ... # type: Column
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
self.speed = ... # type: float
self.cores = ... # type: int
self.threads = ... # type: int
self.address = ... # type: int

View file

@ -15,12 +15,13 @@ from ereuse_devicehub.resources.schemas import Thing, UnitCodes
class Device(Thing):
__doc__ = m.Device.__doc__
id = Integer(description=m.Device.id.comment, dump_only=True)
hid = SanitizedStr(lower=True, dump_only=True, description=m.Device.hid.comment)
tags = NestedOn('Tag',
many=True,
collection_class=OrderedSet,
description='The 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))
manufacturer = SanitizedStr(lower=True, validate=Length(max=STR_SIZE))
serial_number = SanitizedStr(lower=True, data_key='serialNumber')
@ -75,29 +76,54 @@ class Device(Thing):
class Computer(Device):
components = NestedOn('Component', many=True, dump_only=True, collection_class=OrderedSet)
chassis = EnumField(enums.ComputerChassis, required=True)
ram_size = Integer(dump_only=True, data_key='ramSize')
data_storage_size = Integer(dump_only=True, data_key='dataStorageSize')
processor_model = Str(dump_only=True, data_key='processorModel')
graphic_card_model = Str(dump_only=True, data_key='graphicCardModel')
network_speeds = List(Integer(dump_only=True), dump_only=True, data_key='networkSpeeds')
privacy = NestedOn('Event', many=True, dump_only=True, collection_class=set)
__doc__ = m.Computer.__doc__
components = NestedOn('Component',
many=True,
dump_only=True,
collection_class=OrderedSet,
description='The components that are inside this computer.')
chassis = EnumField(enums.ComputerChassis,
required=True,
description=m.Computer.chassis.comment)
ram_size = Integer(dump_only=True,
data_key='ramSize',
description=m.Computer.ram_size.__doc__)
data_storage_size = Integer(dump_only=True,
data_key='dataStorageSize',
description=m.Computer.data_storage_size.__doc__)
processor_model = Str(dump_only=True,
data_key='processorModel',
description=m.Computer.processor_model.__doc__)
graphic_card_model = Str(dump_only=True,
data_key='graphicCardModel',
description=m.Computer.graphic_card_model.__doc__)
network_speeds = List(Integer(dump_only=True),
dump_only=True,
data_key='networkSpeeds',
description=m.Computer.network_speeds.__doc__)
privacy = NestedOn('Event',
many=True,
dump_only=True,
collection_class=set,
description=m.Computer.privacy.__doc__)
class Desktop(Computer):
pass
__doc__ = m.Desktop.__doc__
class Laptop(Computer):
pass
layout = EnumField(Layouts, description=m.Laptop.layout.comment)
__doc__ = m.Laptop.__doc__
class Server(Computer):
pass
__doc__ = m.Server.__doc__
class DisplayMixin:
__doc__ = m.DisplayMixin.__doc__
size = Float(description=m.DisplayMixin.size.comment, validate=Range(2, 150))
technology = EnumField(enums.DisplayTech,
description=m.DisplayMixin.technology.comment)
@ -113,6 +139,8 @@ class DisplayMixin:
class NetworkMixin:
__doc__ = m.NetworkMixin.__doc__
speed = Integer(validate=Range(min=10, max=10000),
unit=UnitCodes.mbps,
description=m.NetworkAdapter.speed.comment)
@ -120,18 +148,20 @@ class NetworkMixin:
class Monitor(DisplayMixin, Device):
pass
__doc__ = m.Monitor.__doc__
class ComputerMonitor(Monitor):
pass
__doc__ = m.ComputerMonitor.__doc__
class TelevisionSet(Monitor):
pass
__doc__ = m.TelevisionSet.__doc__
class Mobile(Device):
__doc__ = m.Mobile.__doc__
imei = Integer(description=m.Mobile.imei.comment)
meid = Str(description=m.Mobile.meid.comment)
@ -149,28 +179,34 @@ class Mobile(Device):
class Smartphone(Mobile):
pass
__doc__ = m.Smartphone.__doc__
class Tablet(Mobile):
pass
__doc__ = m.Tablet.__doc__
class Cellphone(Mobile):
pass
__doc__ = m.Cellphone.__doc__
class Component(Device):
__doc__ = m.Component.__doc__
parent = NestedOn(Device, dump_only=True)
class GraphicCard(Component):
__doc__ = m.GraphicCard.__doc__
memory = Integer(validate=Range(0, 10000),
unit=UnitCodes.mbyte,
description=m.GraphicCard.memory.comment)
class DataStorage(Component):
__doc__ = m.DataStorage.__doc__
size = Integer(validate=Range(0, 10 ** 8),
unit=UnitCodes.mbyte,
description=m.DataStorage.size.comment)
@ -179,128 +215,147 @@ class DataStorage(Component):
class HardDrive(DataStorage):
pass
__doc__ = m.HardDrive.__doc__
class SolidStateDrive(DataStorage):
pass
__doc__ = m.SolidStateDrive.__doc__
class Motherboard(Component):
__doc__ = m.Motherboard.__doc__
slots = Integer(validate=Range(0, 20),
description=m.Motherboard.slots.comment)
usb = Integer(validate=Range(0, 20))
firewire = Integer(validate=Range(0, 20))
serial = Integer(validate=Range(0, 20))
pcmcia = Integer(validate=Range(0, 20))
usb = Integer(validate=Range(0, 20), description=m.Motherboard.usb.comment)
firewire = Integer(validate=Range(0, 20), description=m.Motherboard.firewire.comment)
serial = Integer(validate=Range(0, 20), description=m.Motherboard.serial.comment)
pcmcia = Integer(validate=Range(0, 20), description=m.Motherboard.pcmcia.comment)
class NetworkAdapter(NetworkMixin, Component):
pass
__doc__ = m.NetworkAdapter.__doc__
class Processor(Component):
speed = Float(validate=Range(min=0.1, max=15), unit=UnitCodes.ghz)
cores = Integer(validate=Range(min=1, max=10))
threads = Integer(validate=Range(min=1, max=20))
address = Integer(validate=OneOf({8, 16, 32, 64, 128, 256}))
__doc__ = m.Processor.__doc__
speed = Float(validate=Range(min=0.1, max=15),
unit=UnitCodes.ghz,
description=m.Processor.speed.comment)
cores = Integer(validate=Range(min=1, max=10), description=m.Processor.cores.comment)
threads = Integer(validate=Range(min=1, max=20), description=m.Processor.threads.comment)
address = Integer(validate=OneOf({8, 16, 32, 64, 128, 256}),
description=m.Processor.address.comment)
class RamModule(Component):
size = Integer(validate=Range(min=128, max=17000), unit=UnitCodes.mbyte)
__doc__ = m.RamModule.__doc__
size = Integer(validate=Range(min=128, max=17000),
unit=UnitCodes.mbyte,
description=m.RamModule.size.comment)
speed = Integer(validate=Range(min=100, max=10000), unit=UnitCodes.mhz)
interface = EnumField(enums.RamInterface)
format = EnumField(enums.RamFormat)
class SoundCard(Component):
pass
__doc__ = m.SoundCard.__doc__
class Display(DisplayMixin, Component):
pass
__doc__ = m.Display.__doc__
class Manufacturer(Schema):
__doc__ = m.Manufacturer.__doc__
name = String(dump_only=True)
url = URL(dump_only=True)
logo = URL(dump_only=True)
class ComputerAccessory(Device):
pass
__doc__ = m.ComputerAccessory.__doc__
class Mouse(ComputerAccessory):
pass
__doc__ = m.Mouse.__doc__
class MemoryCardReader(ComputerAccessory):
pass
__doc__ = m.MemoryCardReader.__doc__
class SAI(ComputerAccessory):
pass
__doc__ = m.SAI.__doc__
class Keyboard(ComputerAccessory):
__doc__ = m.Keyboard.__doc__
layout = EnumField(Layouts)
class Networking(NetworkMixin, Device):
pass
__doc__ = m.Networking.__doc__
class Router(Networking):
pass
__doc__ = m.Router.__doc__
class Switch(Networking):
pass
__doc__ = m.Switch.__doc__
class Hub(Networking):
pass
__doc__ = m.Hub.__doc__
class WirelessAccessPoint(Networking):
pass
__doc__ = m.WirelessAccessPoint.__doc__
class Printer(Device):
wireless = Boolean(required=True, missing=False)
scanning = Boolean(required=True, missing=False)
technology = EnumField(enums.PrinterTechnology, required=True)
monochrome = Boolean(required=True, missing=True)
__doc__ = m.Printer.__doc__
wireless = Boolean(required=True, missing=False, description=m.Printer.wireless.comment)
scanning = Boolean(required=True, missing=False, description=m.Printer.scanning.comment)
technology = EnumField(enums.PrinterTechnology,
required=True,
description=m.Printer.technology.comment)
monochrome = Boolean(required=True, missing=True, description=m.Printer.monochrome.comment)
class LabelPrinter(Printer):
pass
__doc__ = m.LabelPrinter.__doc__
class Sound(Device):
pass
__doc__ = m.Sound.__doc__
class Microphone(Sound):
pass
__doc__ = m.Microphone.__doc__
class Video(Device):
pass
__doc__ = m.Video.__doc__
class VideoScaler(Video):
pass
__doc__ = m.VideoScaler.__doc__
class Videoconference(Video):
pass
__doc__ = m.Videoconference.__doc__
class Cooking(Device):
pass
__doc__ = m.Cooking.__doc__
class Mixer(Cooking):
pass
__doc__ = m.Mixer.__doc__

View file

@ -16,6 +16,19 @@ class State(Enum):
class Trading(State):
"""
Trading states.
:cvar Reserved: The device has been reserved.
:cvar Cancelled: The device has been cancelled.
:cvar Sold: The device has been sold.
:cvar Donated: The device is donated.
:cvar Renting: The device is in renting
:cvar ToBeDisposed: The device is disposed.
This is the end of life of a device.
:cvar ProductDisposed: The device has been removed
from the facility. It does not mean end-of-life.
"""
Reserved = e.Reserve
Cancelled = e.CancelTrade
Sold = e.Sell
@ -27,6 +40,16 @@ class Trading(State):
class Physical(State):
"""
Physical states.
:cvar ToBeRepaired: The device has been selected for reparation.
:cvar Repaired: The device has been repaired.
:cvar Preparing: The device is going to be or being prepared.
:cvar Prepared: The device has been prepared.
:cvar ReadyToBeUsed: The device is in working conditions.
:cvar InUse: The device is being reported to be in active use.
"""
ToBeRepaired = e.ToRepair
Repaired = e.Repair
Preparing = e.ToPrepare

View file

@ -278,17 +278,15 @@ class PrinterTechnology(Enum):
class Severity(IntEnum):
"""A flag evaluating the event execution. Ex. failed events
have the value `Severity.Error`.
have the value `Severity.Error`. Devicehub uses 4 severity levels:
Devicehub uses 4 severity levels:
- Info: default neutral severity. The event succeeded.
- Notice: The event succeeded but it is raising awareness.
* Info: default neutral severity. The event succeeded.
* Notice: The event succeeded but it is raising awareness.
Notices are not usually that important but something
(good or bad) worth checking.
- Warning: The event succeeded but there is something important
* Warning: The event succeeded but there is something important
to check negatively affecting the event.
- Error: the event failed.
* Error: the event failed.
Devicehub specially raises user awareness when an event
has a Severity of ``Warning`` or greater.

View file

@ -44,6 +44,10 @@ class JoinedTableMixin:
class Event(Thing):
"""Event performed on a device.
This class extends `Schema's Action <https://schema.org/Action>`_.
"""
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
type = Column(Unicode, nullable=False, index=True)
name = Column(CIText(), default='', nullable=False)
@ -1179,6 +1183,9 @@ class Trade(JoinedTableMixin, EventWithMultipleDevices):
Performing trade events changes the *Trading* state of the
device :class:`ereuse_devicehub.resources.device.states.Trading`.
This class and its inheritors
extend `Schema's Trade <http://schema.org/TradeAction>`_.
"""
shipping_date = Column(DateTime)
shipping_date.comment = """

View file

@ -10,18 +10,19 @@ from teal.resource import Schema
from ereuse_devicehub.marshmallow import NestedOn
from ereuse_devicehub.resources import enums
from ereuse_devicehub.resources.agent.schemas import Agent
from ereuse_devicehub.resources.device.schemas import Component, Computer, Device
from ereuse_devicehub.resources.agent import schemas as s_agent
from ereuse_devicehub.resources.device import schemas as s_device
from ereuse_devicehub.resources.enums import AppearanceRange, Bios, FunctionalityRange, \
PhysicalErasureMethod, PriceSoftware, RATE_POSITIVE, RatingRange, RatingSoftware, ReceiverRole, \
Severity, SnapshotExpectedEvents, SnapshotSoftware, TestDataStorageLength
from ereuse_devicehub.resources.event import models as m
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
from ereuse_devicehub.resources.schemas import Thing
from ereuse_devicehub.resources.user.schemas import User
from ereuse_devicehub.resources.user import schemas as s_user
class Event(Thing):
__doc__ = m.Event.__doc__
id = UUID(dump_only=True)
name = SanitizedStr(default='',
validate=Length(max=STR_BIG_SIZE),
@ -32,31 +33,34 @@ class Event(Thing):
start_time = DateTime(data_key='startTime', description=m.Event.start_time.comment)
end_time = DateTime(data_key='endTime', description=m.Event.end_time.comment)
snapshot = NestedOn('Snapshot', dump_only=True)
agent = NestedOn(Agent, description=m.Event.agent_id.comment)
author = NestedOn(User, dump_only=True, exclude=('token',))
components = NestedOn(Component, dump_only=True, many=True)
parent = NestedOn(Computer, dump_only=True, description=m.Event.parent_id.comment)
agent = NestedOn(s_agent.Agent, description=m.Event.agent_id.comment)
author = NestedOn(s_user.User, dump_only=True, exclude=('token',))
components = NestedOn(s_device.Component, dump_only=True, many=True)
parent = NestedOn(s_device.Computer, dump_only=True, description=m.Event.parent_id.comment)
url = URL(dump_only=True, description=m.Event.url.__doc__)
class EventWithOneDevice(Event):
device = NestedOn(Device, only_query='id')
__doc__ = m.EventWithOneDevice.__doc__
device = NestedOn(s_device.Device, only_query='id')
class EventWithMultipleDevices(Event):
devices = NestedOn(Device, many=True, only_query='id', collection_class=OrderedSet)
__doc__ = m.EventWithMultipleDevices.__doc__
devices = NestedOn(s_device.Device, many=True, only_query='id', collection_class=OrderedSet)
class Add(EventWithOneDevice):
pass
__doc__ = m.Add.__doc__
class Remove(EventWithOneDevice):
pass
__doc__ = m.Remove.__doc__
class Allocate(EventWithMultipleDevices):
to = NestedOn(User,
__doc__ = m.Allocate.__doc__
to = NestedOn(s_user.User,
description='The user the devices are allocated to.')
organization = SanitizedStr(validate=Length(max=STR_SIZE),
description='The organization where the '
@ -64,7 +68,8 @@ class Allocate(EventWithMultipleDevices):
class Deallocate(EventWithMultipleDevices):
from_rel = Nested(User,
__doc__ = m.Deallocate.__doc__
from_rel = Nested(s_user.User,
data_key='from',
description='The user where the devices are not allocated to anymore.')
organization = SanitizedStr(validate=Length(max=STR_SIZE),
@ -73,20 +78,23 @@ class Deallocate(EventWithMultipleDevices):
class EraseBasic(EventWithOneDevice):
__doc__ = m.EraseBasic.__doc__
steps = NestedOn('Step', many=True)
standards = f.List(EnumField(enums.ErasureStandards), dump_only=True)
certificate = URL(dump_only=True)
class EraseSectors(EraseBasic):
pass
__doc__ = m.EraseSectors.__doc__
class ErasePhysical(EraseBasic):
__doc__ = m.ErasePhysical.__doc__
method = EnumField(PhysicalErasureMethod, description=PhysicalErasureMethod.__doc__)
class Step(Schema):
__doc__ = m.Step.__doc__
type = String(description='Only required when it is nested.')
start_time = DateTime(required=True, data_key='startTime')
end_time = DateTime(required=True, data_key='endTime')
@ -94,14 +102,15 @@ class Step(Schema):
class StepZero(Step):
pass
__doc__ = m.StepZero.__doc__
class StepRandom(Step):
pass
__doc__ = m.StepRandom.__doc__
class Rate(EventWithOneDevice):
__doc__ = m.Rate.__doc__
rating = Integer(validate=Range(*RATE_POSITIVE),
dump_only=True,
description=m.Rate.rating.comment)
@ -116,10 +125,11 @@ class Rate(EventWithOneDevice):
class IndividualRate(Rate):
pass
__doc__ = m.IndividualRate.__doc__
class ManualRate(IndividualRate):
__doc__ = m.ManualRate.__doc__
appearance_range = EnumField(AppearanceRange,
required=True,
data_key='appearanceRange',
@ -132,6 +142,7 @@ class ManualRate(IndividualRate):
class WorkbenchRate(ManualRate):
__doc__ = m.WorkbenchRate.__doc__
processor = Float()
ram = Float()
data_storage = Float()
@ -147,6 +158,7 @@ class WorkbenchRate(ManualRate):
class AggregateRate(Rate):
__doc__ = m.AggregateRate.__doc__
workbench = NestedOn(WorkbenchRate, dump_only=True,
description=m.AggregateRate.workbench_id.comment)
manual = NestedOn(ManualRate,
@ -176,6 +188,7 @@ class AggregateRate(Rate):
class Price(EventWithOneDevice):
__doc__ = m.Price.__doc__
currency = EnumField(Currency, required=True, description=m.Price.currency.comment)
price = Decimal(places=m.Price.SCALE,
rounding=m.Price.ROUND,
@ -187,6 +200,8 @@ class Price(EventWithOneDevice):
class EreusePrice(Price):
__doc__ = m.EreusePrice.__doc__
class Service(MarshmallowSchema):
class Type(MarshmallowSchema):
amount = Float()
@ -202,6 +217,7 @@ class EreusePrice(Price):
class Install(EventWithOneDevice):
__doc__ = m.Install.__doc__
name = SanitizedStr(validate=Length(min=4, max=STR_BIG_SIZE),
required=True,
description='The name of the OS installed.')
@ -210,6 +226,7 @@ class Install(EventWithOneDevice):
class Snapshot(EventWithOneDevice):
__doc__ = m.Snapshot.__doc__
"""
The Snapshot updates the state of the device with information about
its components and events performed at them.
@ -229,7 +246,7 @@ class Snapshot(EventWithOneDevice):
'the async Snapshot.')
elapsed = TimeDelta(precision=TimeDelta.SECONDS)
components = NestedOn(Component,
components = NestedOn(s_device.Component,
many=True,
description='A list of components that are inside of the device'
'at the moment of this Snapshot.'
@ -274,10 +291,12 @@ class Snapshot(EventWithOneDevice):
class Test(EventWithOneDevice):
__doc__ = m.Test.__doc__
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
class TestDataStorage(Test):
__doc__ = m.TestDataStorage.__doc__
length = EnumField(TestDataStorageLength, required=True)
status = SanitizedStr(lower=True, validate=Length(max=STR_SIZE), required=True)
lifetime = TimeDelta(precision=TimeDelta.HOURS)
@ -292,55 +311,59 @@ class TestDataStorage(Test):
class StressTest(Test):
pass
__doc__ = m.StressTest.__doc__
class Benchmark(EventWithOneDevice):
__doc__ = m.Benchmark.__doc__
elapsed = TimeDelta(precision=TimeDelta.SECONDS, required=True)
class BenchmarkDataStorage(Benchmark):
__doc__ = m.BenchmarkDataStorage.__doc__
read_speed = Float(required=True, data_key='readSpeed')
write_speed = Float(required=True, data_key='writeSpeed')
class BenchmarkWithRate(Benchmark):
__doc__ = m.BenchmarkWithRate.__doc__
rate = Float(required=True)
class BenchmarkProcessor(BenchmarkWithRate):
pass
__doc__ = m.BenchmarkProcessor.__doc__
class BenchmarkProcessorSysbench(BenchmarkProcessor):
pass
__doc__ = m.BenchmarkProcessorSysbench.__doc__
class BenchmarkRamSysbench(BenchmarkWithRate):
pass
__doc__ = m.BenchmarkRamSysbench.__doc__
class ToRepair(EventWithMultipleDevices):
pass
__doc__ = m.ToRepair.__doc__
class Repair(EventWithMultipleDevices):
pass
__doc__ = m.Repair.__doc__
class ReadyToUse(EventWithMultipleDevices):
pass
__doc__ = m.ReadyToUse.__doc__
class ToPrepare(EventWithMultipleDevices):
pass
__doc__ = m.ToPrepare.__doc__
class Prepare(EventWithMultipleDevices):
pass
__doc__ = m.Prepare.__doc__
class Live(EventWithOneDevice):
__doc__ = m.Live.__doc__
ip = IP(dump_only=True)
subdivision_confidence = Integer(dump_only=True, data_key='subdivisionConfidence')
subdivision = EnumField(Subdivision, dump_only=True)
@ -353,60 +376,63 @@ class Live(EventWithOneDevice):
class Organize(EventWithMultipleDevices):
pass
__doc__ = m.Organize.__doc__
class Reserve(Organize):
pass
__doc__ = m.Reserve.__doc__
class CancelReservation(Organize):
pass
__doc__ = m.CancelReservation.__doc__
class Trade(EventWithMultipleDevices):
__doc__ = m.Trade.__doc__
shipping_date = DateTime(data_key='shippingDate')
invoice_number = SanitizedStr(validate=Length(max=STR_SIZE), data_key='invoiceNumber')
price = NestedOn(Price)
to = NestedOn(Agent, only_query='id', required=True, comment=m.Trade.to_comment)
to = NestedOn(s_agent.Agent, only_query='id', required=True, comment=m.Trade.to_comment)
confirms = NestedOn(Organize)
class Sell(Trade):
pass
__doc__ = m.Sell.__doc__
class Donate(Trade):
pass
__doc__ = m.Donate.__doc__
class Rent(Trade):
pass
__doc__ = m.Rent.__doc__
class CancelTrade(Trade):
pass
__doc__ = m.CancelTrade.__doc__
class ToDisposeProduct(Trade):
pass
__doc__ = m.ToDisposeProduct.__doc__
class DisposeProduct(Trade):
pass
__doc__ = m.DisposeProduct.__doc__
class Receive(EventWithMultipleDevices):
__doc__ = m.Receive.__doc__
role = EnumField(ReceiverRole)
class Migrate(EventWithMultipleDevices):
__doc__ = m.Migrate.__doc__
other = URL()
class MigrateTo(Migrate):
pass
__doc__ = m.MigrateTo.__doc__
class MigrateFrom(Migrate):
pass
__doc__ = m.MigrateFrom.__doc__

View file

@ -2,7 +2,7 @@ from marshmallow import fields as f
from teal.marshmallow import SanitizedStr, URL
from ereuse_devicehub.marshmallow import NestedOn
from ereuse_devicehub.resources.device.schemas import Device
from ereuse_devicehub.resources.device import schemas as s_device
from ereuse_devicehub.resources.lot import models as m
from ereuse_devicehub.resources.models import STR_SIZE
from ereuse_devicehub.resources.schemas import Thing
@ -13,7 +13,7 @@ class Lot(Thing):
name = SanitizedStr(validate=f.validate.Length(max=STR_SIZE), required=True)
description = SanitizedStr(description=m.Lot.description.comment)
closed = f.Boolean(missing=False, description=m.Lot.closed.comment)
devices = NestedOn(Device, many=True, dump_only=True)
devices = NestedOn(s_device.Device, many=True, dump_only=True)
children = NestedOn('Lot', many=True, dump_only=True)
parents = NestedOn('Lot', many=True, dump_only=True)
url = URL(dump_only=True, description=m.Lot.url.__doc__)

View file

@ -9,21 +9,26 @@ STR_XSM_SIZE = 16
class Thing(db.Model):
"""The base class of all Devicehub resources.
This is a loose copy of
`schema.org's Thing class <https://schema.org/Thing>`_
using only needed fields.
"""
__abstract__ = True
# todo make updated to auto-update
updated = db.Column(db.TIMESTAMP(timezone=True),
nullable=False,
index=True,
server_default=db.text('CURRENT_TIMESTAMP'))
updated.comment = """
When this was last changed.
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.
When Devicehub created this.
"""
def __init__(self, **kwargs) -> None:

View file

@ -1,7 +1,9 @@
from enum import Enum
from typing import Any
from marshmallow import post_load
from marshmallow.fields import DateTime, List, String
from marshmallow.schema import SchemaMeta
from teal.marshmallow import URL
from teal.resource import Schema
@ -18,10 +20,59 @@ class UnitCodes(Enum):
kgm = 'KGM'
m = 'MTR'
def __str__(self):
return self.name
# The following SchemaMeta modifications allow us to generate
# documentation using our directive. This is their only purpose.
# Marshmallow's meta class removes variables from our defined
# classes, so we put some home made proxies in order to intercept
# those values and safe them in our classes.
# What we do is:
# 1. Make our ``Meta`` class be the superclass of Marshmallow's
# SchemaMeta and provide a new that stores in class, so we
# can save some vars.
# 2. Substitute SchemaMeta.get_declared_fields with our own method
# that saves more variables.
# Then the directive in our docs/config.py file reads these variables
# generating the documentation.
class Meta(type):
def __new__(cls, *args, **kw) -> Any:
base_name = args[1][0].__name__
y = super().__new__(cls, *args, **kw)
y._base_class = base_name
return y
SchemaMeta.__bases__ = Meta,
@classmethod
def get_declared_fields(mcs, klass, cls_fields, inherited_fields, dict_cls):
klass._own = cls_fields
klass._inherited = inherited_fields
return dict_cls(inherited_fields + cls_fields)
SchemaMeta.get_declared_fields = get_declared_fields
_type_description = """The name of the type of Thing,
like "Device" or "Receive". This is the same as JSON-LD ``@type``.
This field is required when submitting values
so Devicehub knows the type of object. Devicehub always returns this
value.
"""
class Thing(Schema):
type = String(description='Only required when it is nested.')
same_as = List(URL(dump_only=True), dump_only=True, data_key='sameAs')
type = String(description=_type_description)
same_as = List(URL(dump_only=True),
dump_only=True,
data_key='sameAs')
updated = DateTime('iso', dump_only=True, description=m.Thing.updated.comment)
created = DateTime('iso', dump_only=True, description=m.Thing.created.comment)

View file

@ -1,6 +1,3 @@
import datetime
import teal.cache
from flask import Response, current_app as app, g, redirect, request
from flask_sqlalchemy import Pagination
from teal.marshmallow import ValidationError
@ -22,7 +19,6 @@ class TagView(View):
res = self._post_one()
return res
@teal.cache.cache(datetime.timedelta(minutes=1))
def find(self, args: dict):
tags = Tag.query.filter(Tag.is_printable_q()) \
.order_by(Tag.created.desc()) \