diff --git a/server/src/uds/REST/methods/users_groups.py b/server/src/uds/REST/methods/users_groups.py index 2a836d887..63827de60 100644 --- a/server/src/uds/REST/methods/users_groups.py +++ b/server/src/uds/REST/methods/users_groups.py @@ -279,14 +279,14 @@ class Users(DetailHandler): user.delete() except Exception as e: - logger.exception('Removing user') + logger.error('Error on user removal of %s.%s: %s', parent.name, item, e) raise self.invalid_item_response() from e - def servicesPools(self, parent: 'Model', item: str) -> typing.Any: + def servicesPools(self, parent: 'Model', item: str) -> list[dict[str, typing.Any]]: parent = ensure.is_instance(parent, Authenticator) uuid = process_uuid(item) user = parent.users.get(uuid=process_uuid(uuid)) - res = [] + res: list[dict[str, typing.Any]] = [] groups = list(user.get_groups()) for i in get_service_pools_for_groups(groups): res.append( @@ -303,11 +303,11 @@ class Users(DetailHandler): return res - def userServices(self, parent: 'Authenticator', item: str) -> typing.Any: + def userServices(self, parent: 'Authenticator', item: str) -> list[dict[str, typing.Any]]: parent = ensure.is_instance(parent, Authenticator) uuid = process_uuid(item) user = parent.users.get(uuid=process_uuid(uuid)) - res = [] + res: list[dict[str, typing.Any]] = [] for i in user.userServices.all(): if i.state == State.USABLE: v = AssignedService.item_as_dict(i) @@ -336,10 +336,10 @@ class Groups(DetailHandler): q = parent.groups.all().order_by('name') else: q = parent.groups.filter(uuid=process_uuid(item)) - res = [] + res: list[dict[str, typing.Any]] = [] i = None for i in q: - val = { + val: dict[str, typing.Any] = { 'id': i.uuid, 'name': i.name, 'comments': i.comments, @@ -360,7 +360,7 @@ class Groups(DetailHandler): result['pools'] = [v.uuid for v in get_service_pools_for_groups([i])] return result except Exception as e: - logger.exception('REST groups') + logger.error('Group item not found: %s.%s: %s', parent.name, item, e) raise self.invalid_item_response() from e def get_title(self, parent: 'Model') -> str: @@ -434,31 +434,30 @@ class Groups(DetailHandler): fields = self.fields_from_params(valid_fields) is_pattern = fields.get('name', '').find('pat:') == 0 auth = parent.get_instance() + to_save: dict[str, typing.Any] = {} if not item: # Create new if not is_meta and not is_pattern: auth.create_group( fields ) # this throws an exception if there is an error (for example, this auth can't create groups) - toSave = {} for k in valid_fields: - toSave[k] = fields[k] - toSave['comments'] = fields['comments'][:255] - toSave['is_meta'] = is_meta - toSave['meta_if_any'] = meta_if_any - group = parent.groups.create(**toSave) + to_save[k] = fields[k] + to_save['comments'] = fields['comments'][:255] + to_save['is_meta'] = is_meta + to_save['meta_if_any'] = meta_if_any + group = parent.groups.create(**to_save) else: if not is_meta and not is_pattern: auth.modify_group(fields) - toSave = {} for k in valid_fields: - toSave[k] = fields[k] - del toSave['name'] # Name can't be changed - toSave['comments'] = fields['comments'][:255] - toSave['meta_if_any'] = meta_if_any - toSave['skip_mfa'] = fields['skip_mfa'] + to_save[k] = fields[k] + del to_save['name'] # Name can't be changed + to_save['comments'] = fields['comments'][:255] + to_save['meta_if_any'] = meta_if_any + to_save['skip_mfa'] = fields['skip_mfa'] group = parent.groups.get(uuid=process_uuid(item)) - group.__dict__.update(toSave) + group.__dict__.update(to_save) if is_meta: # Do not allow to add meta groups to meta groups diff --git a/server/src/uds/core/services/publication.py b/server/src/uds/core/services/publication.py index 959c97215..10ab03ab1 100644 --- a/server/src/uds/core/services/publication.py +++ b/server/src/uds/core/services/publication.py @@ -97,7 +97,7 @@ class Publication(Environmentable, Serializable): environment: 'Environment', *, service: 'services.Service', - os_manager: typing.Optional['osmanagers.OSManager'] = None, + osmanager: typing.Optional['osmanagers.OSManager'] = None, revision: int = -1, servicepool_name: str = 'Unknown', uuid: str = '', @@ -111,7 +111,7 @@ class Publication(Environmentable, Serializable): Environmentable.__init__(self, environment) Serializable.__init__(self) self._service = service - self._osmanager = os_manager + self._osmanager = osmanager self._revision = revision self._servicepool_name = servicepool_name self._uuid = uuid diff --git a/server/src/uds/core/ui/user_interface.py b/server/src/uds/core/ui/user_interface.py index ffd03ec3b..870bcb3e5 100644 --- a/server/src/uds/core/ui/user_interface.py +++ b/server/src/uds/core/ui/user_interface.py @@ -35,6 +35,7 @@ import codecs import copy import datetime import inspect +import itertools import logging from os import read import pickle # nosec: safe usage @@ -43,6 +44,7 @@ import time import typing import collections.abc import abc +from django.conf import settings from django.utils.translation import gettext @@ -1491,10 +1493,10 @@ class UserInterface(metaclass=UserInterfaceType): if fld_name in values: fld.value = values[fld_name] else: - caller = inspect.stack()[1] - logger.warning( - 'Field %s not found (invoked from %s:%s)', fld_name, caller.filename, caller.lineno - ) + logger.warning('Field %s.%s not found in values data, ', self.__class__.__name__, fld_name) + if settings.DEBUG: + for caller in itertools.islice(inspect.stack(), 1, 8): + logger.warning(' %s:%s:%s', caller.filename, caller.lineno, caller.function) def init_gui(self) -> None: """ @@ -1612,9 +1614,7 @@ class UserInterface(metaclass=UserInterfaceType): return True # For future use, right now we only have one version - _version = values[ - len(SERIALIZATION_HEADER) : len(SERIALIZATION_HEADER) + len(SERIALIZATION_VERSION) - ] + _version = values[len(SERIALIZATION_HEADER) : len(SERIALIZATION_HEADER) + len(SERIALIZATION_VERSION)] values = values[len(SERIALIZATION_HEADER) + len(SERIALIZATION_VERSION) :] diff --git a/server/src/uds/models/group.py b/server/src/uds/models/group.py index df797472c..26312a919 100644 --- a/server/src/uds/models/group.py +++ b/server/src/uds/models/group.py @@ -64,12 +64,12 @@ class Group(UUIDModel): name = models.CharField(max_length=128, db_index=True) state = models.CharField(max_length=1, default=State.ACTIVE, db_index=True) comments = models.CharField(max_length=256, default='') - users = models.ManyToManyField(User, related_name='groups') + users: 'models.ManyToManyField[User, Group]' = models.ManyToManyField(User, related_name='groups') is_meta = models.BooleanField(default=False, db_index=True) # meta_if_any means that if an user belongs to ANY of the groups, it will be considered as belonging to this group # if it is false, the user must belong to ALL of the groups to be considered as belonging to this group meta_if_any = models.BooleanField(default=False) - groups = models.ManyToManyField('self', symmetrical=False) + groups: 'models.ManyToManyField[Group, Group]' = models.ManyToManyField('self', symmetrical=False) created = models.DateTimeField(default=sql_datetime, blank=True) skip_mfa = models.CharField(max_length=1, default=State.INACTIVE, db_index=True) @@ -85,7 +85,7 @@ class Group(UUIDModel): """ return self.deployedServices - class Meta: # pylint: disable=too-few-public-methods + class Meta: # pyright: ignore """ Meta class to declare default order and unique multiple field index """ diff --git a/server/src/uds/models/service_pool_publication.py b/server/src/uds/models/service_pool_publication.py index 29522b249..595cd38f3 100644 --- a/server/src/uds/models/service_pool_publication.py +++ b/server/src/uds/models/service_pool_publication.py @@ -204,7 +204,7 @@ class ServicePoolPublication(UUIDModel): publication_manager().cancel(self) @staticmethod - def pre_delete(sender, **kwargs) -> None: # pylint: disable=unused-argument + def pre_delete(sender: typing.Any, **kwargs: typing.Any) -> None: # pylint: disable=unused-argument """ Used to invoke the Service class "Destroy" before deleting it from database. diff --git a/server/src/uds/services/Proxmox/service_fixed.py b/server/src/uds/services/Proxmox/service_fixed.py index 0ce902b05..1e635fdcd 100644 --- a/server/src/uds/services/Proxmox/service_fixed.py +++ b/server/src/uds/services/Proxmox/service_fixed.py @@ -56,6 +56,7 @@ if typing.TYPE_CHECKING: logger = logging.getLogger(__name__) + class ProxmoxServiceFixed(FixedService): # pylint: disable=too-many-public-methods """ Proxmox fixed machines service. diff --git a/server/src/uds/services/Test/deployment.py b/server/src/uds/services/Test/deployment.py index eb306294e..06501a448 100644 --- a/server/src/uds/services/Test/deployment.py +++ b/server/src/uds/services/Test/deployment.py @@ -111,6 +111,11 @@ class TestUserService(services.UserService): logger.info('Deploying for user %s %s', user, self.data) self.data.count = 3 return types.states.State.RUNNING + + def deploy_for_cache(self, level: int) -> types.states.State: + logger.info('Deploying for cache %s %s', level, self.data) + self.data.count = 3 + return types.states.State.RUNNING def check_state(self) -> types.states.State: logger.info('Checking state of deployment %s', self.data) diff --git a/server/tests/services/proxmox/fixtures.py b/server/tests/services/proxmox/fixtures.py index 148584fa3..1a78f6716 100644 --- a/server/tests/services/proxmox/fixtures.py +++ b/server/tests/services/proxmox/fixtures.py @@ -41,7 +41,6 @@ import uuid from uds.core import types, environment from uds.core.ui.user_interface import gui -from uds.services.OpenNebula.on import vm from ...utils.test import UDSTestCase from ...utils.autospec import autospec, AutoSpecMethodInfo @@ -317,11 +316,11 @@ CLIENT_METHODS_INFO: typing.Final[list[AutoSpecMethodInfo]] = [ # list_machines AutoSpecMethodInfo('list_machines', return_value=VMS_INFO), # get_machine_pool_info - AutoSpecMethodInfo('get_machine_pool_info', method=lambda vmid, poolid, **kwargs: VMS_INFO[vmid - 1]), + AutoSpecMethodInfo('get_machine_pool_info', method=lambda vmid, poolid, **kwargs: VMS_INFO[vmid - 1]), # pyright: ignore # get_machine_info - AutoSpecMethodInfo('get_machine_info', method=lambda vmid, *args, **kwargs: VMS_INFO[vmid - 1]), + AutoSpecMethodInfo('get_machine_info', method=lambda vmid, *args, **kwargs: VMS_INFO[vmid - 1]), # pyright: ignore # get_machine_configuration - AutoSpecMethodInfo('get_machine_configuration', method=lambda vmid, **kwargs: VMS_CONFIGURATION[vmid - 1]), + AutoSpecMethodInfo('get_machine_configuration', method=lambda vmid, **kwargs: VMS_CONFIGURATION[vmid - 1]), # pyright: ignore # set_machine_ha return None # start_machine AutoSpecMethodInfo('start_machine', return_value=UPID), @@ -340,24 +339,24 @@ CLIENT_METHODS_INFO: typing.Final[list[AutoSpecMethodInfo]] = [ # get_storage AutoSpecMethodInfo( 'get_storage', - method=lambda storage, node, **kwargs: next(filter(lambda s: s.storage == storage, STORAGES)), + method=lambda storage, node, **kwargs: next(filter(lambda s: s.storage == storage, STORAGES)), # pyright: ignore ), # list_storages AutoSpecMethodInfo( 'list_storages', - method=lambda node, **kwargs: ( - (list(filter(lambda s: s.node == node, STORAGES))) if node is not None else STORAGES + method=lambda node, **kwargs: ( # pyright: ignore + (list(filter(lambda s: s.node == node, STORAGES))) if node is not None else STORAGES # pyright: ignore ), ), # get_node_stats AutoSpecMethodInfo( - 'get_node_stats', method=lambda node, **kwargs: next(filter(lambda n: n.name == node, NODE_STATS)) + 'get_node_stats', method=lambda node, **kwargs: next(filter(lambda n: n.name == node, NODE_STATS)) # pyright: ignore ), # list_pools AutoSpecMethodInfo('list_pools', return_value=POOLS), # get_pool_info AutoSpecMethodInfo( - 'get_pool_info', method=lambda poolid, **kwargs: next(filter(lambda p: p.poolid == poolid, POOLS)) + 'get_pool_info', method=lambda poolid, **kwargs: next(filter(lambda p: p.poolid == poolid, POOLS)) # pyright: ignore ), # get_console_connection AutoSpecMethodInfo('get_console_connection', return_value=CONSOLE_CONNECTION_INFO), @@ -390,9 +389,11 @@ SERVICE_LINKED_VALUES_DICT: typing.Final[gui.ValuesDictType] = { } SERVICE_FIXED_VALUES_DICT: typing.Final[gui.ValuesDictType] = { + 'token': '', 'pool': POOLS[0].poolid, 'machines': [str(VMS_INFO[2].vmid), str(VMS_INFO[3].vmid), str(VMS_INFO[4].vmid)], 'use_snapshots': True, + 'prov_uuid': '', } diff --git a/server/tests/services/proxmox/test_service_fixed.py b/server/tests/services/proxmox/test_service_fixed.py index 04ae45f21..3029e6666 100644 --- a/server/tests/services/proxmox/test_service_fixed.py +++ b/server/tests/services/proxmox/test_service_fixed.py @@ -64,7 +64,7 @@ class TestProxmovFixedService(UDSTransactionTestCase): api.test.assert_called_with() def test_service_methods_1(self) -> None: - with fixtures.patch_provider_api() as api: + with fixtures.patch_provider_api(): service = fixtures.create_service_fixed() self.assertEqual(service.get_machine_info(2).name, fixtures.VMS_INFO[1].name) @@ -72,7 +72,7 @@ class TestProxmovFixedService(UDSTransactionTestCase): # is_available is already tested, so we will skip it # Enumerate assignables - locate_vm = lambda vmid: next( + locate_vm: typing.Callable[[str], typing.Any] = lambda vmid: next( (x for x in fixtures.VMS_INFO if x.vmid == int(vmid)), fixtures.VMS_INFO[0] ) @@ -109,7 +109,7 @@ class TestProxmovFixedService(UDSTransactionTestCase): self.assertEqual(service._get_assigned_machines(), set([vmid, vmid2])) def test_service_methods_2(self) -> None: - with fixtures.patch_provider_api() as api: + with fixtures.patch_provider_api(): service = fixtures.create_service_fixed() # Get machine name diff --git a/server/tests/utils/autospec.py b/server/tests/utils/autospec.py index 523b69b45..55423463c 100644 --- a/server/tests/utils/autospec.py +++ b/server/tests/utils/autospec.py @@ -37,10 +37,10 @@ from unittest import mock class AutoSpecMethodInfo: name: str return_value: typing.Any = None - method: 'typing.Callable|None' = None + method: 'typing.Callable[..., typing.Any]|None' = None -def autospec(cls: type, metods_info: collections.abc.Iterable, **kwargs: typing.Any) -> mock.Mock: +def autospec(cls: type, metods_info: collections.abc.Iterable[AutoSpecMethodInfo], **kwargs: typing.Any) -> mock.Mock: """ This is a helper function that will create a mock object with the same methods as the class passed as parameter. This is useful for testing purposes, where you want to mock a class and still have the same methods available.