Improve api generation; remove unneeded views

This commit is contained in:
Xavier Bustamante Talavera 2018-06-24 16:57:49 +02:00
parent ac6c94ebe4
commit 10f3aa7d35
13 changed files with 172 additions and 59 deletions

View File

@ -13,6 +13,7 @@ from ereuse_devicehub.resources.event import AddDef, AggregateRateDef, AppRateDe
from ereuse_devicehub.resources.inventory import InventoryDef
from ereuse_devicehub.resources.tag import TagDef
from ereuse_devicehub.resources.user import OrganizationDef, UserDef
from teal.auth import TokenAuth
from teal.config import Config
@ -43,6 +44,14 @@ class DevicehubConfig(Config):
It is used by default, for example, when creating tags.
"""
API_DOC_CONFIG_TITLE = 'Devicehub'
API_DOC_CONFIG_VERSION = '0.2'
API_DOC_CONFIG_COMPONENTS = {
'securitySchemes': {
'bearerAuth': TokenAuth.API_DOCS
}
}
API_DOC_CLASS_DISCRIMINATOR = 'type'
def __init__(self, db: str = None) -> None:
if not self.ORGANIZATION_NAME or not self.ORGANIZATION_TAX_ID:

View File

@ -13,64 +13,80 @@ class DeviceDef(Resource):
class ComputerDef(DeviceDef):
VIEW = None
SCHEMA = Computer
class DesktopDef(ComputerDef):
VIEW = None
SCHEMA = Desktop
class LaptopDef(ComputerDef):
VIEW = None
SCHEMA = Laptop
class NetbookDef(ComputerDef):
VIEW = None
SCHEMA = Netbook
class ServerDef(ComputerDef):
VIEW = None
SCHEMA = Server
class MicrotowerDef(ComputerDef):
VIEW = None
SCHEMA = Microtower
class ComputerMonitorDef(DeviceDef):
VIEW = None
SCHEMA = ComputerMonitor
class ComponentDef(DeviceDef):
VIEW = None
SCHEMA = Component
class GraphicCardDef(ComponentDef):
VIEW = None
SCHEMA = GraphicCard
class DataStorageDef(ComponentDef):
VIEW = None
SCHEMA = DataStorage
class HardDriveDef(DataStorageDef):
VIEW = None
SCHEMA = HardDrive
class SolidStateDriveDef(DataStorageDef):
VIEW = None
SCHEMA = SolidStateDrive
class MotherboardDef(ComponentDef):
VIEW = None
SCHEMA = Motherboard
class NetworkAdapterDef(ComponentDef):
VIEW = None
SCHEMA = NetworkAdapter
class RamModuleDef(ComponentDef):
VIEW = None
SCHEMA = RamModule
class ProcessorDef(ComponentDef):
VIEW = None
SCHEMA = Processor

View File

@ -18,6 +18,10 @@ from teal.db import CASCADE, POLYMORPHIC_ID, POLYMORPHIC_ON, ResourceNotFound, c
class Device(Thing):
"""
Base class for any type of physical object that can be identified.
"""
id = Column(BigInteger, Sequence('device_seq'), primary_key=True)
id.comment = """
The identifier of the device for this database.
@ -45,12 +49,18 @@ class Device(Thing):
"""
depth = Column(Float(decimal_return_scale=3), check_range('depth', 0.1, 3))
color = Column(ColorType)
color.comment = """
"""
@property
def events(self) -> list:
"""
All the events performed to the device,
ordered by ascending creation time.
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.
Events are returned by ascending creation time.
"""
return sorted(chain(self.events_multiple, self.events_one), key=attrgetter('created'))

View File

@ -1,7 +1,6 @@
from marshmallow import post_load, pre_load
from marshmallow.fields import Float, Integer, Str
from marshmallow.validate import Length, OneOf, Range
from marshmallow_enum import EnumField
from sqlalchemy.util import OrderedSet
from ereuse_devicehub.marshmallow import NestedOn
@ -9,21 +8,29 @@ from ereuse_devicehub.resources.device import models as m
from ereuse_devicehub.resources.enums import ComputerMonitorTechnologies, RamFormat, RamInterface
from ereuse_devicehub.resources.models import STR_BIG_SIZE, STR_SIZE
from ereuse_devicehub.resources.schemas import Thing, UnitCodes
from teal.marshmallow import ValidationError
from teal.marshmallow import EnumField, ValidationError
class Device(Thing):
id = Integer(description=m.Device.id, dump_only=True)
hid = Str(dump_only=True, description=m.Device.hid)
tags = NestedOn('Tag', many=True, collection_class=OrderedSet)
id = Integer(description=m.Device.id.comment.strip(), dump_only=True)
hid = Str(dump_only=True, description=m.Device.hid.comment.strip())
tags = NestedOn('Tag',
many=True,
collection_class=OrderedSet,
description='The set of tags that identify the device.')
model = Str(validate=Length(max=STR_BIG_SIZE))
manufacturer = Str(validate=Length(max=STR_SIZE))
serial_number = Str(data_key='serialNumber')
product_id = Str(data_key='productId')
weight = Float(validate=Range(0.1, 3), unit=UnitCodes.kgm, description=m.Device.weight)
width = Float(validate=Range(0.1, 3), unit=UnitCodes.m, description=m.Device.width)
height = Float(validate=Range(0.1, 3), unit=UnitCodes.m, description=m.Device.height)
events = NestedOn('Event', many=True, dump_only=True)
weight = Float(validate=Range(0.1, 3),
unit=UnitCodes.kgm,
description=m.Device.weight.comment.strip())
width = Float(validate=Range(0.1, 3),
unit=UnitCodes.m,
description=m.Device.width.comment.strip())
height = Float(validate=Range(0.1, 3),
unit=UnitCodes.m,
description=m.Device.height.comment.strip())
events = NestedOn('Event', many=True, dump_only=True, description=m.Device.events.__doc__)
events_one = NestedOn('Event', many=True, load_only=True, collection_class=OrderedSet)
@pre_load
@ -76,15 +83,15 @@ class Microtower(Computer):
class ComputerMonitor(Device):
size = Float(description=m.ComputerMonitor.size.comment, validate=Range(2, 150))
size = Float(description=m.ComputerMonitor.size.comment.strip(), validate=Range(2, 150))
technology = EnumField(ComputerMonitorTechnologies,
description=m.ComputerMonitor.technology.comment)
description=m.ComputerMonitor.technology.comment.strip())
resolution_width = Integer(data_key='resolutionWidth',
validate=Range(10, 20000),
description=m.ComputerMonitor.resolution_width.comment)
description=m.ComputerMonitor.resolution_width.comment.strip())
resolution_height = Integer(data_key='resolutionHeight',
validate=Range(10, 20000),
description=m.ComputerMonitor.resolution_height.comment)
description=m.ComputerMonitor.resolution_height.comment.strip())
class Component(Device):
@ -101,9 +108,6 @@ class DataStorage(Component):
size = Integer(validate=Range(0, 10 ** 8),
unit=UnitCodes.mbyte,
description='The size of the hard-drive in MB.')
erasure = NestedOn('EraseBasic', load_only=True)
tests = NestedOn('TestHardDrive', many=True, load_only=True)
benchmarks = NestedOn('BenchmarkHardDrive', load_only=True, many=True)
class HardDrive(DataStorage):

View File

@ -3,12 +3,29 @@ from teal.resource import View
class DeviceView(View):
def get(self, id):
"""
Devices view
---
description: Gets a device or multiple devices.
parameters:
- name: id
type: integer
in: path
description: The identifier of the device.
responses:
200:
description: The device or devices.
"""
return super().get(id)
def one(self, id: int):
"""Gets one device."""
device = Device.query.filter_by(id=id).one()
return self.schema.jsonify(device)
def find(self, args: dict):
"""Gets many devices"""
"""Gets many devices."""
devices = Device.query.all()
return self.schema.jsonify(devices, many=True)

View File

@ -18,62 +18,77 @@ class EventDef(Resource):
class AddDef(EventDef):
VIEW = None
SCHEMA = Add
class RemoveDef(EventDef):
VIEW = None
SCHEMA = Remove
class EraseBasicDef(EventDef):
VIEW = None
SCHEMA = EraseBasic
class EraseSectorsDef(EraseBasicDef):
VIEW = None
SCHEMA = EraseSectors
class StepDef(Resource):
VIEW = None
SCHEMA = Step
class StepZeroDef(StepDef):
VIEW = None
SCHEMA = StepZero
class StepRandomDef(StepDef):
VIEW = None
SCHEMA = StepRandom
class RateDef(EventDef):
VIEW = None
SCHEMA = Rate
class AggregateRateDef(RateDef):
VIEW = None
SCHEMA = AggregateRate
class WorkbenchRateDef(RateDef):
VIEW = None
SCHEMA = WorkbenchRate
class PhotoboxUserDef(RateDef):
VIEW = None
SCHEMA = PhotoboxUserRate
class PhotoboxSystemRateDef(RateDef):
VIEW = None
SCHEMA = PhotoboxSystemRate
class AppRateDef(RateDef):
VIEW = None
SCHEMA = AppRate
class InstallDef(EventDef):
VIEW = None
SCHEMA = Install
class SnapshotDef(EventDef):
VIEW = None
SCHEMA = Snapshot
VIEW = SnapshotView
@ -86,36 +101,45 @@ class SnapshotDef(EventDef):
class TestDef(EventDef):
VIEW = None
SCHEMA = Test
class TestDataStorageDef(TestDef):
VIEW = None
SCHEMA = TestDataStorage
class StressTestDef(TestDef):
VIEW = None
SCHEMA = StressTest
class BenchmarkDef(EventDef):
VIEW = None
SCHEMA = Benchmark
class BenchmarkDataStorageDef(BenchmarkDef):
VIEW = None
SCHEMA = BenchmarkDataStorage
class BenchmarkWithRateDef(BenchmarkDef):
VIEW = None
SCHEMA = BenchmarkWithRate
class BenchmarkProcessorDef(BenchmarkWithRateDef):
VIEW = None
SCHEMA = BenchmarkProcessor
class BenchmarkProcessorSysbenchDef(BenchmarkProcessorDef):
VIEW = None
SCHEMA = BenchmarkProcessorSysbench
class BenchmarkRamSysbenchDef(BenchmarkWithRateDef):
VIEW = None
SCHEMA = BenchmarkRamSysbench

View File

@ -46,8 +46,7 @@ class Event(Thing):
closed.comment = """
Whether the author has finished the event.
After this is set to True, no modifications are allowed.
By default are events are closed when performed.
By default events are closed when performed.
"""
error = Column(Boolean, default=False, nullable=False)
error.comment = """

View File

@ -3,7 +3,6 @@ from marshmallow import ValidationError, validates_schema
from marshmallow.fields import Boolean, DateTime, Float, Integer, List, Nested, String, TimeDelta, \
UUID
from marshmallow.validate import Length, Range
from marshmallow_enum import EnumField
from ereuse_devicehub.marshmallow import NestedOn
from ereuse_devicehub.resources.device.schemas import Component, Device
@ -13,21 +12,23 @@ 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 teal.marshmallow import Version
from teal.marshmallow import EnumField, Version
from teal.resource import Schema
class Event(Thing):
id = UUID(dump_only=True)
name = String(default='', validate=Length(STR_BIG_SIZE), description=m.Event.name.comment)
date = DateTime('iso', description=m.Event.date.comment)
error = Boolean(default=False, description=m.Event.error.comment)
incidence = Boolean(default=False, description=m.Event.incidence.comment)
name = String(default='',
validate=Length(STR_BIG_SIZE),
description=m.Event.name.comment.strip())
date = DateTime('iso', description=m.Event.date.comment.strip())
error = Boolean(default=False, description=m.Event.error.comment.strip())
incidence = Boolean(default=False, description=m.Event.incidence.comment.strip())
snapshot = NestedOn('Snapshot', dump_only=True)
components = NestedOn(Component, dump_only=True, many=True)
description = String(default='', description=m.Event.description.comment)
description = String(default='', description=m.Event.description.comment.strip())
author = NestedOn(User, dump_only=True, exclude=('token',))
closed = Boolean(missing=True, description=m.Event.closed.comment)
closed = Boolean(missing=True, description=m.Event.closed.comment.strip())
class EventWithOneDevice(Event):

View File

@ -8,8 +8,7 @@ from sqlalchemy.util import OrderedSet
from ereuse_devicehub.db import db
from ereuse_devicehub.resources.device.models import Component, Computer
from ereuse_devicehub.resources.enums import RatingSoftware, SnapshotSoftware
from ereuse_devicehub.resources.event.models import Event, ManualRate, Snapshot, TestDataStorage, \
WorkbenchRate
from ereuse_devicehub.resources.event.models import Event, ManualRate, Snapshot, WorkbenchRate
from teal.resource import View
@ -85,16 +84,3 @@ class SnapshotView(View):
ret = self.schema.jsonify(snapshot) # transform it back
ret.status_code = 201
return ret
class TestHardDriveView(View):
def post(self):
t = request.get_json() # type: dict
# noinspection PyArgumentList
test = TestDataStorage(snapshot_id=t.pop('snapshot'), device_id=t.pop('device'), **t)
return test
class StressTestView(View):
def post(self):
t = request.get_json() # type: dict

View File

@ -58,13 +58,39 @@ class InventoryView(View):
sort = Nested(Sorting, missing=[Device.created.desc()])
page = Integer(validate=Range(min=1), missing=1)
def find(self, args: dict):
"""
Supports the inventory view of ``devicehub-client``; returns
def get(self, id):
"""Inventory view
---
description: Supports the inventory view of ``devicehub-client``; returns
all the devices, groups and widgets of this Devicehub instance.
The result can be filtered, sorted, and paginated.
responses:
200:
description: The inventory.
schema:
type: object
properties:
devices:
type: array
items:
$ref: '#/definitions/Device'
pagination:
type: object
properties:
page:
type: integer
minimum: 0
perPage:
type: integer
minimum: 0
total:
type: integer
minimum: 0
"""
# todo .format(yaml.load(schema2parameters(self.FindArgs, default_in='path', name='path')))
return super().get(id)
def find(self, args: dict):
"""See :meth:`.get` above."""
devices = Device.query \
.filter(*args['filter']) \
.order_by(*args['sort']) \

View File

@ -22,8 +22,8 @@ class Thing(Schema):
type = String(description='Only required when it is nested.')
url = URL(dump_only=True, description='The URL of the resource.')
same_as = List(URL(dump_only=True), dump_only=True, data_key='sameAs')
updated = DateTime('iso', dump_only=True, description=m.Thing.updated)
created = DateTime('iso', dump_only=True, description=m.Thing.created)
updated = DateTime('iso', dump_only=True, description=m.Thing.updated.comment.strip())
created = DateTime('iso', dump_only=True, description=m.Thing.created.comment.strip())
@post_load
def remove_type(self, data: dict):

View File

@ -1,6 +1,6 @@
import pytest
from ereuse_devicehub.devicehub import Devicehub
from ereuse_devicehub.client import Client
def test_dependencies():
@ -12,6 +12,27 @@ def test_dependencies():
# noinspection PyArgumentList
def test_init(app: Devicehub):
"""Tests app initialization."""
pass
def test_api_docs(client: Client):
"""Tests /apidocs correct initialization."""
docs, _ = client.get('/apidocs')
assert set(docs['paths'].keys()) == {
'/tags/{id}/device',
'/inventories/',
'/apidocs',
'/users/',
'/devices/',
'/tags/',
'/snapshots/',
'/users/login',
'/events/'
}
assert docs['info'] == {'title': 'Devicehub', 'version': '0.2'}
assert docs['components']['securitySchemes']['bearerAuth'] == {
'description': 'Basic scheme with token.',
'in': 'header',
'description:': 'HTTP Basic scheme',
'type': 'http',
'scheme': 'basic',
'name': 'Authorization'
}
assert len(docs['definitions']) == 46

View File

@ -313,13 +313,13 @@ def test_erase(user: UserClient):
snapshot = snapshot_and_check(user, s, ('EraseSectors',), perform_second_snapshot=True)
storage, *_ = snapshot['components']
assert storage['type'] == 'SolidStateDrive', 'Components must be ordered by input order'
storage, _ = user.get(res=SolidStateDrive, item=storage['id']) # Let's get storage events too
storage, _ = user.get(res=Device, item=storage['id']) # Let's get storage events too
# order: creation time descending
_snapshot1, erasure1, _snapshot2, erasure2 = storage['events']
assert erasure1['type'] == erasure2['type'] == 'EraseSectors'
assert _snapshot1['type'] == _snapshot2['type'] == 'Snapshot'
assert snapshot == user.get(res=Event, item=_snapshot2['id'])[0]
erasure, _ = user.get(res=EraseBasic, item=erasure1['id'])
erasure, _ = user.get(res=Event, item=erasure1['id'])
assert len(erasure['steps']) == 2
assert erasure['steps'][0]['startTime'] == '2018-06-01T08:15:00+00:00'
assert erasure['steps'][0]['endTime'] == '2018-06-01T09:16:00+00:00'