added logic to the skill manifest endpoint to reconcile the manifest uploaded by the device with the manifest stored on the database

pull/187/head
Chris Veilleux 2019-06-19 22:18:32 -05:00
parent a85f8d7723
commit 93a212e673
21 changed files with 578 additions and 193 deletions

View File

@ -107,7 +107,7 @@ def validate_response(context):
response_data['membership'], has_item('id')
)
assert_that(len(response_data['agreements']), equal_to(2))
assert_that(len(response_data['agreements']), equal_to(3))
agreement = response_data['agreements'][0]
assert_that(agreement['type'], equal_to(PRIVACY_POLICY))
assert_that(

View File

@ -1,4 +1,5 @@
import json
from datetime import datetime
from http import HTTPStatus
from logging import getLogger
@ -8,33 +9,82 @@ from schematics.types import (
StringType,
ModelType,
ListType,
DateTimeType,
IntType,
BooleanType
BooleanType,
TimestampType
)
from selene.api import PublicEndpoint
from selene.data.device import DeviceSkillRepository
from selene.data.device import ManifestSkill, DeviceSkillRepository
from selene.data.skill import SkillRepository
class SkillManifest(Model):
class SkillManifestReconciler(object):
def __init__(self, db, skill_manifest, device_skills):
self.db = db
self.skill_manifest_repo = DeviceSkillRepository(db)
self.skill_repo = SkillRepository(self.db)
self.device_manifest = {sm.skill_gid: sm for sm in skill_manifest}
self.db_manifest = {ds.skill_gid: ds for ds in device_skills}
self.device_manifest_global_ids = {
gid for gid in self.device_manifest.keys()
}
self.db_manifest_global_ids = {gid for gid in self.db_manifest}
def reconcile(self):
self._update_skills()
self._remove_skills()
self._add_skills()
def _update_skills(self):
common_global_ids = self.device_manifest_global_ids.intersection(
self.db_manifest_global_ids
)
for gid in common_global_ids:
if self.device_manifest[gid] == self.db_manifest[gid]:
self.skill_manifest_repo.update_manifest_skill(
self.device_manifest[gid]
)
def _remove_skills(self):
skills_to_remove = self.db_manifest_global_ids.difference(
self.device_manifest_global_ids
)
for gid in skills_to_remove:
manifest_skill = self.db_manifest[gid]
self.skill_manifest_repo.remove_manifest_skill(manifest_skill)
if manifest_skill.device_id in gid:
self.skill_repo.remove_by_gid(gid)
def _add_skills(self):
skills_to_add = self.device_manifest_global_ids.difference(
self.db_manifest_global_ids
)
for gid in skills_to_add:
skill_id = self.skill_repo.ensure_skill_exists(gid)
self.skill_manifest_repo.add_manifest_skill(
skill_id,
self.device_manifest[gid]
)
class RequestManifestSkill(Model):
name = StringType(required=True)
origin = StringType(default='')
installation = StringType(default='')
origin = StringType(required=True)
installation = StringType(required=True)
failure_message = StringType(default='')
status = StringType(default='')
beta = BooleanType(default='')
installed = DateTimeType()
updated = DateTimeType()
update = DateTimeType()
skill_gid = StringType()
status = StringType(required=True)
beta = BooleanType(required=True)
installed = TimestampType(required=True)
updated = TimestampType(required=True)
skill_gid = StringType(required=True)
class SkillJson(Model):
class SkillManifestRequest(Model):
blacklist = ListType(StringType)
version = IntType()
skills = ListType(ModelType(SkillManifest, required=True))
skills = ListType(ModelType(RequestManifestSkill, required=True))
_log = getLogger(__package__)
@ -69,9 +119,12 @@ class DeviceSkillManifestEndpoint(PublicEndpoint):
return response
def _build_skill_manifest(self, device_id):
device_skills = self.device_skill_repo.get_skills_for_device(device_id)
skills_manifest = []
for skill in device_skills:
"""Convert the DeviceSkill dataclass into the format for the response"""
skill_manifest = self.device_skill_repo.get_skill_manifest_for_device(
device_id
)
response_skills = []
for skill in skill_manifest:
response_skill = dict(
origin=skill.install_method,
installation=skill.install_status,
@ -85,15 +138,50 @@ class DeviceSkillManifestEndpoint(PublicEndpoint):
if skill.update_ts is not None:
response_skill['updated'] = skill.update_ts.timestamp()
skills_manifest.append(response_skill)
skills_manifest = {'skills': skills_manifest}
response_skills.append(response_skill)
return json.dumps(skills_manifest)
return json.dumps({'skills': response_skills})
def put(self, device_id):
self._authenticate(device_id)
payload = json.loads(self.request.data)
skill_json = SkillJson(payload)
skill_json.validate()
SkillRepository(self.db).update_skills_manifest(device_id, payload['skills'])
self._validate_put_request()
self._update_skill_manifest(device_id)
return '', HTTPStatus.OK
def _validate_put_request(self):
request_data = SkillManifestRequest(self.request.json)
request_data.validate()
def _update_skill_manifest(self, device_id):
db_skill_manifest = self.device_skill_repo.get_skill_manifest_for_device(
device_id
)
device_skill_manifest = []
for manifest_skill in self.request.json['skills']:
self._convert_manifest_timestamps(manifest_skill)
device_skill_manifest.append(
ManifestSkill(
device_id=device_id,
install_method=manifest_skill['origin'],
install_status=manifest_skill['installation'],
install_failure_reason=manifest_skill.get('failure_message'),
install_ts=manifest_skill['installed'],
skill_gid=manifest_skill['skill_gid'],
update_ts=manifest_skill['updated']
)
)
reconciler = SkillManifestReconciler(
self.db,
device_skill_manifest,
db_skill_manifest
)
reconciler.reconcile()
@staticmethod
def _convert_manifest_timestamps(manifest_skill):
for key in ('installed', 'updated'):
value = manifest_skill[key]
if value:
manifest_skill[key] = datetime.fromtimestamp(value)
else:
manifest_skill[key] = None

View File

@ -1,8 +1,47 @@
Feature: Upload and fetch skills manifest
Feature: Device can upload and fetch skills manifest
Scenario: A skill manifest is successfully uploaded
Given a device with a skill
When a skill manifest is uploaded
Then the skill manifest endpoint should return 200 status code
And the skill manifest should be added
Scenario: Device retrieves its manifest from the API
Given an authorized device
When a device requests its skill manifest
Then the request will be successful
And the response will contain the manifest
Scenario: Device uploads an unchanged manifest
Given an authorized device
When a device uploads a skill manifest without changes
Then the request will be successful
And the skill manifest on the database is unchanged
And device last contact timestamp is updated
Scenario: Device uploads a manifest with an updated skill
Given an authorized device
When a device uploads a skill manifest with an updated skill
Then the request will be successful
And the skill manifest on the database is unchanged
And device last contact timestamp is updated
Scenario: Device uploads a manifest with a deleted skill
Given an authorized device
When a device uploads a skill manifest with a deleted skill
Then the request will be successful
And the skill is removed from the manifest on the database
And device last contact timestamp is updated
@device_specific_skill
Scenario: Device uploads a manifest with a deleted device-specific skill
Given an authorized device
When a device uploads a skill manifest with a deleted device-specific skill
Then the request will be successful
And the device-specific skill is removed from the manifest on the database
And the device-specific skill is removed from the database
And device last contact timestamp is updated
@new_skill
Scenario: Device uploads a manifest with a new skill
Given an authorized device
When a device uploads a skill manifest with a new skill
Then the request will be successful
And the skill is added to the database
And the skill is added to the manifest on the database
And device last contact timestamp is updated

View File

@ -3,12 +3,16 @@ from behave import fixture, use_fixture
from public_api.api import public
from selene.api import generate_device_login
from selene.api.etag import ETagManager
from selene.data.device import PreferenceRepository
from selene.testing.account import add_account, remove_account
from selene.testing.agreement import add_agreements, remove_agreements
from selene.testing.account_geography import add_account_geography
from selene.testing.account_preference import add_account_preference
from selene.testing.device import add_device
from selene.testing.device_skill import (
add_skill_to_manifest,
remove_manifest_skill
)
from selene.testing.skill import add_skill, remove_skill
from selene.testing.text_to_speech import (
add_text_to_speech,
remove_text_to_speech
@ -55,7 +59,16 @@ def before_scenario(context, _):
context.wake_word = add_wake_word(context.db)
context.voice = add_text_to_speech(context.db)
_add_device(context)
except Exception:
context.skill = add_skill(
context.db,
skill_global_id='selene-test-skill|19.02'
)
context.manifest_skill = add_skill_to_manifest(
context.db,
context.device_id,
context.skill
)
except:
import traceback
print(traceback.print_exc())
@ -64,6 +77,7 @@ def after_scenario(context, _):
remove_account(context.db, context.account)
remove_wake_word(context.db, context.wake_word)
remove_text_to_speech(context.db, context.voice)
remove_skill(context.db, skill_global_id=context.skill.skill_gid)
def _add_device(context):
@ -71,3 +85,29 @@ def _add_device(context):
context.device_id = device_id
context.device_name = 'Selene Test Device'
context.device_login = generate_device_login(device_id, context.cache)
context.access_token = context.device_login['accessToken']
def before_tag(context, tag):
if tag == 'device_specific_skill':
_add_device_specific_skil(context)
def _add_device_specific_skil(context):
context.device_specific_skill = add_skill(
context.db,
skill_global_id='@{device_id}|device-specific-skill|19.02'.format(
device_id=context.device_id
)
)
context.device_specific_manifest = add_skill_to_manifest(
context.db,
context.device_id,
context.skill
)
def after_tag(context, tag):
if tag == 'new_skill':
remove_manifest_skill(context.db, context.new_manifest_skill)
remove_skill(context.db, context.new_skill.skill_gid)

View File

@ -1,95 +1,217 @@
import json
from datetime import datetime
from http import HTTPStatus
from behave import given, when, then
from hamcrest import assert_that, equal_to
from behave import when, then
from hamcrest import (
assert_that,
equal_to,
has_entry,
has_key,
is_in,
is_,
none,
not_none,
not_
)
skill_manifest = {
"blacklist": [],
"version": 1,
"skills": [
{
"name": "fallback-wolfram-alpha",
"origin": "default",
"beta": False,
"status": "active",
"installed": datetime.now().timestamp(),
"updated": datetime.now().timestamp(),
"installation": "installed",
"update": 0,
"skill_gid": "fallback-wolfram-alpha|19.02",
}
]
}
from selene.data.device import DeviceSkillRepository, ManifestSkill
from selene.data.skill import SkillRepository, Skill
skill = {
'skill_gid': 'fallback-wolfram-alpha|19.02',
'skillMetadata': {
'sections': [
{
'name': 'section1',
'fields': [
{
'label': 'test'
}
]
}
]
def _build_manifest_upload(manifest_skills):
upload_skills = []
for skill in manifest_skills:
upload_skills.append(
dict(
name="test-skill-name",
origin=skill.install_method,
beta=False,
status="active",
installed=skill.install_ts.timestamp(),
updated=skill.update_ts.timestamp(),
installation=skill.install_status,
skill_gid=skill.skill_gid,
)
)
return {
"blacklist": [],
"version": 1,
"skills": upload_skills
}
}
@given('a device with a skill')
def device_skill(context):
login = context.device_login
device_id = login['uuid']
access_token = login['accessToken']
headers = dict(Authorization='Bearer {token}'.format(token=access_token))
context.client.put(
'/v1/device/{uuid}/skill'.format(uuid=device_id),
data=json.dumps(skill),
content_type='application_json',
headers=headers
)
@when('a skill manifest is uploaded')
def device_skill_manifest(context):
login = context.device_login
device_id = login['uuid']
access_token = login['accessToken']
headers = dict(Authorization='Bearer {token}'.format(token=access_token))
context.upload_skill_manifest_response = context.client.put(
'/v1/device/{uuid}/skillJson'.format(uuid=device_id),
data=json.dumps(skill_manifest),
content_type='application_json',
headers=headers
)
@then('the skill manifest endpoint should return 200 status code')
def validate_skill_manifest_upload(context):
response = context.upload_skill_manifest_response
assert_that(response.status_code, equal_to(HTTPStatus.OK))
@then('the skill manifest should be added')
@when('a device requests its skill manifest')
def get_skill_manifest(context):
login = context.device_login
device_id = login['uuid']
access_token = login['accessToken']
headers = dict(Authorization='Bearer {token}'.format(token=access_token))
response = context.client.get(
'/v1/device/{uuid}/skillJson'.format(uuid=device_id),
headers=headers
context.response = context.client.get(
'/v1/device/{device_id}/skillJson'.format(device_id=context.device_id),
content_type='application/json',
headers=context.request_header
)
assert_that(response.status_code, equal_to(HTTPStatus.OK))
skill_manifest_from_db = json.loads(response.data)
skill = skill_manifest_from_db['skills'][0]
expected_skill = skill_manifest['skills'][0]
assert_that(skill['origin'], equal_to(expected_skill['origin']))
assert_that(skill['installation'], equal_to(expected_skill['installation']))
assert_that(skill['installed'], equal_to(expected_skill['installed']))
assert_that(skill['updated'], equal_to(expected_skill['updated']))
@when('a device uploads a skill manifest without changes')
def upload_unchanged_skill_manifest(context):
skill_manifest = _build_manifest_upload([context.manifest_skill])
_upload_skill_manifest(context, skill_manifest)
@when('a device uploads a skill manifest with an updated skill')
def upload_unchanged_skill_manifest(context):
skill_manifest = _build_manifest_upload([context.manifest_skill])
context.update_ts = datetime.utcnow().timestamp()
skill_manifest['skills'][0]['updated'] = context.update_ts
_upload_skill_manifest(context, skill_manifest)
@when('a device uploads a skill manifest with a deleted skill')
def upload_unchanged_skill_manifest(context):
skill_manifest = _build_manifest_upload([])
_upload_skill_manifest(context, skill_manifest)
@when('a device uploads a skill manifest with a deleted device-specific skill')
def upload_skill_manifest_no_device_specific(context):
skill_manifest = _build_manifest_upload([context.manifest_skill])
_upload_skill_manifest(context, skill_manifest)
@when('a device uploads a skill manifest with a new skill')
def upload_unchanged_skill_manifest(context):
context.new_skill = Skill(skill_gid='new-test-skill|19.02')
context.new_manifest_skill = ManifestSkill(
device_id=context.device_id,
install_method='test_install_method',
install_status='test_install_status',
skill_gid=context.new_skill.skill_gid,
install_ts=datetime.utcnow(),
update_ts=datetime.utcnow()
)
skill_manifest = _build_manifest_upload(
[context.manifest_skill, context.new_manifest_skill]
)
_upload_skill_manifest(context, skill_manifest)
def _upload_skill_manifest(context, skill_manifest):
context.response = context.client.put(
'/v1/device/{device_id}/skillJson'.format(device_id=context.device_id),
data=json.dumps(skill_manifest),
content_type='application/json',
headers=context.request_header
)
@then('the response will contain the manifest')
def check_skill_manifest_response(context):
response = context.response.json
assert_that(response, has_key('skills'))
assert_that(len(response['skills']), equal_to(1))
manifest_skill = response['skills'][0]
assert_that(
manifest_skill,
has_entry('origin', context.manifest_skill.install_method)
)
assert_that(
manifest_skill,
has_entry('installation', context.manifest_skill.install_status))
assert_that(
manifest_skill,
has_entry(
'failure_message',
context.manifest_skill.install_failure_reason
)
)
assert_that(
manifest_skill,
has_entry('installed', context.manifest_skill.install_ts.timestamp())
)
assert_that(
manifest_skill,
has_entry('updated', context.manifest_skill.update_ts.timestamp())
)
assert_that(
manifest_skill,
has_entry('skill_gid', context.manifest_skill.skill_gid)
)
@then('the skill manifest on the database is unchanged')
def get_unchanged_skill_manifest(context):
device_skill_repo = DeviceSkillRepository(context.db)
skill_manifest = device_skill_repo.get_skill_manifest_for_device(
context.device_id
)
assert_that(len(skill_manifest), equal_to(1))
manifest_skill = skill_manifest[0]
assert_that(manifest_skill, equal_to(context.manifest_skill))
@then('the skill manifest on the database is updated')
def get_updated_skill_manifest(context):
device_skill_repo = DeviceSkillRepository(context.db)
skill_manifest = device_skill_repo.get_skill_manifest_for_device(
context.device_id
)
assert_that(len(skill_manifest), equal_to(1))
manifest_skill = skill_manifest[0]
assert_that(manifest_skill, not_(equal_to(context.manifest_skill)))
manifest_skill.update_ts = context.update_ts
assert_that(manifest_skill, (equal_to(context.manifest_skill)))
@then('the skill is removed from the manifest on the database')
def get_empty_skill_manifest(context):
device_skill_repo = DeviceSkillRepository(context.db)
skill_manifest = device_skill_repo.get_skill_manifest_for_device(
context.device_id
)
assert_that(len(skill_manifest), equal_to(0))
@then('the device-specific skill is removed from the manifest on the database')
def get_skill_manifest_no_device_specific(context):
device_skill_repo = DeviceSkillRepository(context.db)
skill_manifest = device_skill_repo.get_skill_manifest_for_device(
context.device_id
)
assert_that(len(skill_manifest), equal_to(1))
remaining_skill = skill_manifest[0]
assert_that(
remaining_skill.skill_gid,
not_(equal_to(context.device_specific_skill.skill_gid))
)
@then('the device-specific skill is removed from the database')
def ensure_device_specific_skill_removed(context):
skill_repo = SkillRepository(context.db)
skill = skill_repo.get_skill_by_global_id(
context.device_specific_skill.skill_gid
)
assert_that(skill, is_(none()))
@then('the skill is added to the manifest on the database')
def get_skill_manifest_new_skill(context):
device_skill_repo = DeviceSkillRepository(context.db)
skill_manifest = device_skill_repo.get_skill_manifest_for_device(
context.device_id
)
assert_that(len(skill_manifest), equal_to(2))
assert_that(context.manifest_skill, is_in(skill_manifest))
# the device_skill id is not part of the request data so clear it out
for manifest_skill in skill_manifest:
if manifest_skill.skill_gid == context.new_skill.skill_gid:
manifest_skill.id = None
assert_that(context.new_manifest_skill, is_in(skill_manifest))
@then('the skill is added to the database')
def get_new_skill(context):
skill_repo = SkillRepository(context.db)
skill = skill_repo.get_skill_by_global_id(
context.new_skill.skill_gid
)
assert_that(skill, is_(not_none()))

View File

@ -10,6 +10,6 @@ OPEN_DATASET = 'Open Dataset'
class Agreement(object):
type: str
version: str
content: str
effective_date: date
id: str = None
content: str = None

View File

@ -5,11 +5,26 @@ from datetime import datetime
@dataclass
class DeviceSkill(object):
id: str
device_name: str
device_id: str
install_method: str
install_status: str
skill_id: str
skill_gid: str
device_name: str = None
install_failure_reason: str = None
install_ts: datetime = None
update_ts: datetime = None
skill_settings: dict = None
skill_settings_display_id: str = None
@dataclass
class ManifestSkill(object):
device_id: str
install_method: str
install_status: str
skill_gid: str
install_failure_reason: str = None
install_ts: datetime = None
update_ts: datetime = None
id: str = None

View File

@ -106,7 +106,7 @@ class DeviceRepository(RepositoryBase):
)
self.cursor.update(db_request)
def add_wake_word(self, account_id: str, wake_word: WakeWord) -> str:
def add_wake_word(self, wake_word: WakeWord, account_id: str = None) -> str:
"""Adds a row to the wake word table
:param account_id: the account that we are linking to the wake word

View File

@ -1,5 +1,8 @@
"""Data repository code for the skills on a device"""
from ..entity.device_skill import DeviceSkill
from dataclasses import asdict
from typing import List
from ..entity.device_skill import DeviceSkill, ManifestSkill
from ...repository_base import RepositoryBase
@ -7,20 +10,15 @@ class DeviceSkillRepository(RepositoryBase):
def __init__(self, db):
super(DeviceSkillRepository, self).__init__(db, __file__)
def get_installed_skills_for_account(self, account_id: str):
def get_installed_skills_for_account(
self, account_id: str
) -> List[DeviceSkill]:
return self._select_all_into_dataclass(
dataclass=DeviceSkill,
sql_file_name='get_device_skills_for_account.sql',
args=dict(account_id=account_id)
)
def get_skills_for_device(self, device_id: str):
return self._select_all_into_dataclass(
dataclass=DeviceSkill,
sql_file_name='get_skills_for_device.sql',
args=dict(device_id=device_id)
)
def update_skill_settings(
self, account_id: str, device_names: tuple, skill_name: str
):
@ -33,3 +31,39 @@ class DeviceSkillRepository(RepositoryBase):
)
)
self.cursor.update(db_request)
def get_skill_manifest_for_device(self, device_id: str):
return self._select_all_into_dataclass(
dataclass=ManifestSkill,
sql_file_name='get_device_skill_manifest.sql',
args=dict(device_id=device_id)
)
def update_manifest_skill(self, manifest_skill: ManifestSkill):
db_request = self._build_db_request(
sql_file_name='update_skill_manifest.sql',
args=asdict(manifest_skill)
)
self.cursor.update(db_request)
def add_manifest_skill(self, skill_id: str, manifest_skill: ManifestSkill):
db_request_args = dict(skill_id=skill_id)
db_request_args.update(asdict(manifest_skill))
db_request = self._build_db_request(
sql_file_name='add_manifest_skill.sql',
args=db_request_args
)
db_result = self.cursor.insert_returning(db_request)
return db_result['id']
def remove_manifest_skill(self, manifest_skill: ManifestSkill):
db_request = self._build_db_request(
sql_file_name='remove_manifest_skill.sql',
args=dict(
device_id=manifest_skill.device_id,
skill_gid=manifest_skill.skill_gid
)
)
self.cursor.delete(db_request)

View File

@ -0,0 +1,22 @@
INSERT INTO
device.device_skill (
device_id,
skill_id,
install_method,
install_status,
install_failure_reason,
install_ts,
update_ts
)
VALUES
(
%(device_id)s,
%(skill_id)s,
%(install_method)s,
%(install_status)s,
%(install_failure_reason)s,
%(install_ts)s,
%(update_ts)s
)
RETURNING
id

View File

@ -1,11 +1,10 @@
SELECT
d.name AS device_name,
ds.id,
ds.device_id,
ds.install_failure_reason,
ds.install_method,
ds.install_status,
ds.install_ts,
ds.skill_id,
ds.update_ts,
s.skill_gid
FROM

View File

@ -1,11 +1,14 @@
SELECT
d.name AS device_name,
ds.id,
ds.device_id,
ds.install_failure_reason,
ds.install_method,
ds.install_status,
ds.install_ts,
ds.settings,
ds.skill_id,
ds.skill_settings_display_id,
ds.update_ts,
s.skill_gid
FROM

View File

@ -0,0 +1,5 @@
DELETE FROM
device.device_skill
WHERE
device_id = %(device_id)s
AND skill_id = (SELECT id FROM skill.skill WHERE skill_gid = %(skill_gid)s)

View File

@ -0,0 +1,18 @@
UPDATE
device.device_skill
SET
install_method = %(install_method)s,
install_status = %(install_status)s,
install_failure_reason = %(failure_message)s,
install_ts = %(install_ts)s,
update_ts = %(update_ts)s
WHERE
device_id = %(device_id)s
AND skill_id = (
SELECT
id
FROM
skill.skill
WHERE
skill_gid = %(skill_gid)s
)

View File

@ -1,4 +1,5 @@
from .entity.display import SkillDisplay
from .entity.skill import Skill
from .entity.skill_setting import AccountSkillSetting
from .repository.display import SkillDisplayRepository
from .repository.setting import SkillSettingRepository

View File

@ -1,5 +1,4 @@
import json
from datetime import datetime
from typing import List
from selene.util.db import use_transaction
@ -100,6 +99,13 @@ class SkillRepository(RepositoryBase):
return skills
def get_skill_by_global_id(self, skill_global_id):
return self._select_one_into_dataclass(
dataclass=Skill,
sql_file_name='get_skill_by_global_id.sql',
args=dict(skill_global_id=skill_global_id)
)
@use_transaction
def add(self, device_id: str, skill: dict) -> str:
skill['skill_gid'] = skill.get('skill_gid') or skill.get('identifier')
@ -127,44 +133,11 @@ class SkillRepository(RepositoryBase):
result = '', ''
return result
def update_skills_manifest(self, device_id: str, skill_manifest):
for skill in skill_manifest:
skill['device_id'] = device_id
self._convert_to_datetime(skill)
db_batch_request = self._build_db_batch_request(
'update_skill_manifest.sql',
args=skill_manifest
)
self.cursor.batch_update(db_batch_request)
def _convert_to_datetime(self, skill):
installed = skill.get('installed')
if installed and installed != 0:
installed = datetime.fromtimestamp(installed)
skill['installed'] = installed
else:
skill['installed'] = datetime.now()
updated = skill.get('updated')
if updated and updated != 0:
updated = datetime.fromtimestamp(updated)
skill['updated'] = updated
else:
skill['updated'] = datetime.now()
failure_message = skill.get('failure_message')
if failure_message is None:
skill['failure_message'] = ''
def ensure_skill_exists(self, skill_gid: str) -> str:
skill = self._select_one_into_dataclass(
dataclass=Skill,
sql_file_name='get_skill_by_global_id.sql',
args=dict(skill_gid=skill_gid)
)
def ensure_skill_exists(self, skill_global_id: str) -> str:
skill = self.get_skill_by_global_id(skill_global_id)
if skill is None:
family_name = _parse_skill_gid(skill_gid)
skill_id = self._add_skill(skill_gid, family_name)
family_name = _parse_skill_gid(skill_global_id)
skill_id = self._add_skill(skill_global_id, family_name)
else:
skill_id = skill.id
@ -184,3 +157,10 @@ class SkillRepository(RepositoryBase):
skill_id = db_result.id
return skill_id
def remove_by_gid(self, skill_gid):
db_request = self._build_db_request(
sql_file_name='remove_skill_by_gid.sql',
args=dict(skill_gid=skill_gid)
)
self.cursor.delete(db_request)

View File

@ -4,4 +4,4 @@ SELECT
FROM
skill.skill
WHERE
skill_gid = %(skill_gid)s;
skill_gid = %(skill_global_id)s;

View File

@ -0,0 +1,4 @@
DELETE FROM
skill.skill
WHERE
skill_gid = %(skill_gid)s

View File

@ -1,23 +0,0 @@
UPDATE
device.device_skill dev_skill
SET
install_method = %(origin)s,
install_status = %(installation)s,
install_failure_reason = %(failure_message)s,
install_ts = %(installed)s,
update_ts = %(updated)s
WHERE
id IN (
SELECT
dev_skill.id
FROM
device.device dev
INNER JOIN
device.device_skill dev_skill ON dev.id = dev_skill.device_id
INNER JOIN
skill.skill skill ON dev_skill.skill_id = skill.id
LEFT JOIN
skill.display skill_disp ON skill.id = skill_disp.skill_id
WHERE
dev.id = %(device_id)s AND (skill.family_name = %(name)s or skill_disp.display_data->>'name' = %(name)s)
)

View File

@ -0,0 +1,25 @@
from datetime import datetime
from selene.data.device import ManifestSkill, DeviceSkillRepository
def add_skill_to_manifest(db, device_id, skill):
manifest_skill = ManifestSkill(
device_id=device_id,
install_method='test_install_method',
install_status='test_install_status',
skill_gid=skill.skill_gid,
install_ts=datetime.utcnow(),
update_ts=datetime.utcnow()
)
device_skill_repo = DeviceSkillRepository(db)
manifest_skill.id = device_skill_repo.add_manifest_skill(
skill.id,
manifest_skill
)
return manifest_skill
def remove_manifest_skill(db, manifest_skill):
device_skill_repo = DeviceSkillRepository(db)
device_skill_repo.remove_manifest_skill(manifest_skill)

View File

@ -0,0 +1,13 @@
from selene.data.skill import Skill, SkillRepository
def add_skill(db, skill_global_id):
skill_repo = SkillRepository(db)
skill_id = skill_repo.ensure_skill_exists(skill_global_id=skill_global_id)
return Skill(skill_global_id, skill_id)
def remove_skill(db, skill_global_id):
skill_repo = SkillRepository(db)
skill_repo.remove_by_gid(skill_gid=skill_global_id)