app: Introduce API for managing setup state of the app

Useful for replacing setup_helper. This API should be considered EXPERIMENTAL
and may change.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Sunil Mohan Adapa 2021-11-18 13:32:34 -08:00 committed by James Valleroy
parent 929e7f6dba
commit 47cb5e027f
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
2 changed files with 110 additions and 0 deletions

View File

@ -4,6 +4,8 @@ Base class for all Freedombox applications.
""" """
import collections import collections
import enum
import sys
from . import clients as clients_module from . import clients as clients_module
@ -40,6 +42,12 @@ class App:
_all_apps = collections.OrderedDict() _all_apps = collections.OrderedDict()
class SetupState(enum.Enum):
"""Various states of app being setup."""
NEEDS_SETUP = 'needs-setup'
NEEDS_UPDATE = 'needs-update'
UP_TO_DATE = 'up-to-date'
def __init__(self): def __init__(self):
"""Build the app by adding components. """Build the app by adding components.
@ -118,6 +126,51 @@ class App:
for component in self.components.values(): for component in self.components.values():
component.setup(old_version=old_version) component.setup(old_version=old_version)
def get_setup_state(self) -> SetupState:
"""Return whether the app is not setup or needs upgrade."""
current_version = self.get_setup_version()
if current_version and self.info.version <= current_version:
return self.SetupState.UP_TO_DATE
# If an app needs installing/updating but no setup method is available,
# then automatically set version.
#
# Minor violation of 'get' only discipline for convenience.
module = sys.modules[self.__module__]
if not hasattr(module, 'setup'):
self.set_setup_version(self.info.version)
return self.SetupState.UP_TO_DATE
if not current_version:
return self.SetupState.NEEDS_SETUP
return self.SetupState.NEEDS_UPDATE
def get_setup_version(self) -> int:
"""Return the setup version of the app."""
# XXX: Optimize version gets
from . import models
try:
app_entry = models.Module.objects.get(pk=self.app_id)
return app_entry.setup_version
except models.Module.DoesNotExist:
return 0
def needs_setup(self) -> bool:
"""Return whether the app needs to be setup.
A simple shortcut for get_setup_state() == NEEDS_SETUP
"""
return self.get_setup_state() == self.SetupState.NEEDS_SETUP
def set_setup_version(self, version: int) -> None:
"""Set the app's setup version."""
from . import models
models.Module.objects.update_or_create(
pk=self.app_id, defaults={'setup_version': version})
def enable(self): def enable(self):
"""Enable all the components of the app.""" """Enable all the components of the app."""
for component in self.components.values(): for component in self.components.values():

View File

@ -4,6 +4,7 @@ Test module for App, base class for all applications.
""" """
import collections import collections
import sys
from unittest.mock import Mock, call, patch from unittest.mock import Mock, call, patch
import pytest import pytest
@ -18,6 +19,16 @@ class AppTest(App):
app_id = 'test-app' app_id = 'test-app'
class AppSetupTest(App):
"""Sample App for testing setup operations."""
app_id = 'test-app-setup'
def __init__(self):
super().__init__()
info = Info('test-app-setup', 3)
self.add(info)
class LeaderTest(FollowerComponent): class LeaderTest(FollowerComponent):
"""Test class for using LeaderComponent in tests.""" """Test class for using LeaderComponent in tests."""
is_leader = True is_leader = True
@ -124,6 +135,52 @@ def test_app_setup(app_with_components):
component.setup.assert_has_calls([call(old_version=2)]) component.setup.assert_has_calls([call(old_version=2)])
@pytest.mark.django_db
def test_setup_state():
"""Test retrieving the current setup state of the app."""
app = AppSetupTest()
app.info.version = 3
module = sys.modules[__name__]
app.set_setup_version(3)
assert app.get_setup_state() == App.SetupState.UP_TO_DATE
assert not app.needs_setup()
app.set_setup_version(0)
try:
delattr(module, 'setup')
except AttributeError:
pass
assert app.get_setup_state() == App.SetupState.UP_TO_DATE
assert not app.needs_setup()
assert app.get_setup_version() == 3
setattr(module, 'setup', True)
app.set_setup_version(0)
assert app.get_setup_state() == App.SetupState.NEEDS_SETUP
assert app.needs_setup()
app.set_setup_version(2)
assert app.get_setup_state() == App.SetupState.NEEDS_UPDATE
assert not app.needs_setup()
@pytest.mark.django_db
def test_get_set_setup_version():
"""Setting and getting the setup version of the app."""
app = AppSetupTest()
from plinth import models
try:
models.Module.objects.get(pk=app.app_id).delete()
except models.Module.DoesNotExist:
pass
assert app.get_setup_version() == 0
app.set_setup_version(5)
assert app.get_setup_version() == 5
def test_app_enable(app_with_components): def test_app_enable(app_with_components):
"""Test that enabling an app enables components.""" """Test that enabling an app enables components."""
app_with_components.disable() app_with_components.disable()