Skip to content

Commit

Permalink
Start testing the pytlas assistant manager (pam)
Browse files Browse the repository at this point in the history
  • Loading branch information
YuukanOO committed Dec 11, 2018
1 parent 0770708 commit f8560a0
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 28 deletions.
65 changes: 37 additions & 28 deletions pytlas/pam.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from pytlas.skill import handlers, module_metas
from pytlas.localization import module_translations
from pytlas.utils import get_package_name_from_module
from pytlas.skill_data import SkillData
from shutil import rmtree
import re, logging, os, subprocess

Expand Down Expand Up @@ -93,29 +94,6 @@ def from_skill_folder(folder):

return parts[0]


class SkillData:
"""Represents a single skill data.
"""

def __init__(self, package, name=None, description='No description provided',
version='?.?.?', author='', homepage='', media=''):
self.package = package
self.name = name or package
self.media = media
self.description = description
self.version = version
self.author = author
self.homepage = homepage

def __str__(self):
return """{name} - v{version}
description: {description}
homepage: {homepage}
author: {author}
package: {package}
""".format(**self.__dict__)

def install_dependencies_if_needed(directory, stdout=None):
"""Install skill dependencies if a requirements.txt file is present.
Expand Down Expand Up @@ -146,8 +124,13 @@ def install_skills(directory, stdout=None, *names):
stdout (func): Function to call to output something
names (list of str): list of skills to install
Returns:
list of str: List of installed or updated skills
"""

installed_skills = []

for name in names:
url = name
owner, repo = skill_parts_from_name(name)
Expand All @@ -165,8 +148,10 @@ def install_skills(directory, stdout=None, *names):
if os.path.isdir(dest):
if stdout:
stdout(' Skill folder already exists, updating')

logging.warning('Skill already exists, will update it')

installed_skills.extend(update_skills(directory, stdout, name))

continue

Expand All @@ -178,17 +163,21 @@ def install_skills(directory, stdout=None, *names):

logging.info('Successfully cloned skill "%s"' % repo)

install_dependencies_if_needed(dest, stdout)
install_dependencies_if_needed(dest, stdout)

if stdout:
stdout(' Installed! ✔️')

logging.info('Successfully installed "%s"' % repo)

installed_skills.append(name)
except subprocess.CalledProcessError as e:
if stdout:
stdout(' Failed ❌')

logging.error("Could not clone the skill repo, make sure you didn't mispelled it and you have sufficient rights to clone it. \"%s\"" % e)

return installed_skills

def update_skills(directory, stdout, *names):
"""Update given skills.
Expand All @@ -198,8 +187,12 @@ def update_skills(directory, stdout, *names):
stdout (func): Function to call to output something
names (list of str): list of skills to update
Returns:
list of str: List of updated skills
"""

updated_skills = []
names = names or os.listdir(directory)

for name in names:
Expand All @@ -225,15 +218,22 @@ def update_skills(directory, stdout, *names):
try:
subprocess.check_output(['git', 'pull'], shell=True, cwd=folder, stderr=subprocess.STDOUT)

install_dependencies_if_needed(folder, stdout)

if stdout:
stdout(' Updated ✔️')

logging.info('Updated "%s"' % name)

updated_skills.append(name)

except subprocess.CalledProcessError as e:
if stdout:
stdout(' Failed to update ❌')

logging.error('Could not pull in "%s": "%s"' % (folder, e))

return updated_skills

def uninstall_skills(directory, stdout, *names):
"""Uninstall given skills.
Expand All @@ -243,8 +243,13 @@ def uninstall_skills(directory, stdout, *names):
stdout (func): Function to call to output something
names (list of str): list of skills to uninstall
Returns:
list of str: List of removed skills
"""

removed_skills = []

for name in names:
if stdout:
stdout('Processing %s' % name)
Expand All @@ -264,13 +269,17 @@ def uninstall_skills(directory, stdout, *names):
if stdout:
stdout(' Uninstalled ✔️')

logging.info('Uninstalled "%s"' % repo)
logging.info('Uninstalled "%s"' % name)

removed_skills.append(name)
except Exception as e:
if stdout:
stdout(' Failed ❌')

logging.error('Could not delete the "%s" skill folder: "%s"' % (folder, e))

return removed_skills

def get_installed_skills(lang):
"""Retrieve installed and loaded skills. You must call import_skills first if
you want them to show in the results.
Expand All @@ -283,7 +292,7 @@ def get_installed_skills(lang):
"""

unique_pkgs = list(set(from_skill_folder(get_package_name_from_module(v.__module__)) for v in handlers.values()))
unique_pkgs = list(set(get_package_name_from_module(v.__module__) for v in handlers.values()))
skills = []

for pkg in unique_pkgs:
Expand All @@ -294,6 +303,6 @@ def get_installed_skills(lang):
translations = module_translations.get(pkg, {}).get(lang, {})
meta = meta_func(lambda k: translations.get(k, k))

skills.append(SkillData(pkg, **meta))
skills.append(SkillData(from_skill_folder(pkg), **meta))

return skills
21 changes: 21 additions & 0 deletions pytlas/skill_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class SkillData:
"""Represents a single skill data.
"""

def __init__(self, package, name=None, description='No description provided',
version='?.?.?', author='', homepage='', media=''):
self.package = package
self.name = name or package
self.media = media
self.description = description
self.version = version
self.author = author
self.homepage = homepage

def __str__(self):
return """{name} - v{version}
description: {description}
homepage: {homepage}
author: {author}
package: {package}
""".format(**self.__dict__)
46 changes: 46 additions & 0 deletions tests/test_pam.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from sure import expect
from unittest.mock import patch
from pytlas.skill import register, register_metadata
from pytlas.pam import get_installed_skills
from pytlas.skill_data import SkillData

class TestPam:

def test_it_should_retrieve_installed_skills_from_handlers(self):
register('get_weather', lambda r: r.agent.done(), 'atlassistant__weather')

with patch('pytlas.pam.get_package_name_from_module', return_value='atlassistant__weather'):
r = get_installed_skills('fr')

expect(r).to.have.length_of(1)

data = r[0]

expect(data).to.be.a(SkillData)
expect(data.name).to.equal('atlassistant/weather')
expect(data.version).to.equal('?.?.?')

def test_it_should_list_installed_skill_metadata_when_available(self):

def get_meta(_): return {
'name': 'weather',
'version': '1.0.0',
}

register('get_weather', lambda r: r.agent.done(), 'atlassistant__weather')
register_metadata(get_meta, 'atlassistant__weather')

with patch('pytlas.pam.get_package_name_from_module', return_value='atlassistant__weather'):
r = get_installed_skills('fr')

expect(r).to.have.length_of(1)

data = r[0]

expect(data).to.be.a(SkillData)
expect(data.name).to.equal('weather')
expect(data.package).to.equal('atlassistant/weather')
expect(data.version).to.equal('1.0.0')

def test_it_should_localize_installed_skill_metadata_when_available(self):
pass
32 changes: 32 additions & 0 deletions tests/test_skill_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from sure import expect
from pytlas.skill_data import SkillData

TEST_PACKAGE = 'atlassistant__weather'
TEST_NAME = 'weather'
TEST_DESCRIPTION = 'Give weather forecasts'
TEST_VERSION = '1.1.0'
TEST_AUTHOR = 'Julien LEICHER'
TEST_HOMEPAGE = 'https://julien.leicher.me'

class TestSkillData:

def test_it_should_contain_package_as_the_name_if_not_defined(self):
data = SkillData(package=TEST_PACKAGE)

expect(data.name).to.equal(data.package)

def test_it_should_print_correctly(self):
data = SkillData(
package=TEST_PACKAGE,
name=TEST_NAME,
description=TEST_DESCRIPTION,
version=TEST_VERSION,
homepage=TEST_HOMEPAGE,
author=TEST_AUTHOR)

expect(str(data)).to.equal("""%s - v%s
description: %s
homepage: %s
author: %s
package: %s
""" % (TEST_NAME, TEST_VERSION, TEST_DESCRIPTION, TEST_HOMEPAGE, TEST_AUTHOR, TEST_PACKAGE))

0 comments on commit f8560a0

Please sign in to comment.