From fe8e53de14449f58eed1a90ca37962b033cf7b6f Mon Sep 17 00:00:00 2001 From: UnDarkle Date: Wed, 12 Sep 2018 17:38:45 +0300 Subject: [PATCH 01/18] automated create sub resources --- src/rotest/common/utils.py | 28 ++++++++++++++++++++++++++ src/rotest/core/abstract_test.py | 20 ++---------------- src/rotest/core/block.py | 28 +++++--------------------- src/rotest/management/base_resource.py | 24 ++++++++++++++++++++-- 4 files changed, 57 insertions(+), 43 deletions(-) diff --git a/src/rotest/common/utils.py b/src/rotest/common/utils.py index 1f5ca794..2e39c946 100644 --- a/src/rotest/common/utils.py +++ b/src/rotest/common/utils.py @@ -72,3 +72,31 @@ def get_work_dir(base_dir, test_name, test_item): os.makedirs(work_dir) return work_dir + + +def get_class_fields(cls, field_type): + """Get all fields of the class that inherit from the given type. + + * This method searches also in the parent classes. + * Fields of sons override fields of parents. + * Ignores fields starting with '_'. + + Args: + cls (type): class to search. + field_type (type): field type to find. + + Yields: + tuple. all found (field name, field value). + """ + if cls is object: + return + + for parent_class in cls.__bases__: + for (field_name, field) in get_class_fields(parent_class, field_type): + yield (field_name, field) + + for field_name in cls.__dict__: + if not field_name.startswith("_"): + field = getattr(cls, field_name) + if isinstance(field, field_type): + yield (field_name, field) diff --git a/src/rotest/core/abstract_test.py b/src/rotest/core/abstract_test.py index 0dd6ebc2..f55f973c 100644 --- a/src/rotest/core/abstract_test.py +++ b/src/rotest/core/abstract_test.py @@ -13,6 +13,7 @@ from ipdbugger import debug from attrdict import AttrDict +from rotest.common.utils import get_class_fields from rotest.core.models.case_data import TestOutcome from rotest.management.base_resource import BaseResource from rotest.management.client.manager import ResourceRequest @@ -101,23 +102,6 @@ def release_resource_loggers(self): for resource in self.all_resources.itervalues(): resource.release_logger(self.logger) - @classmethod - def get_resource_requests_fields(cls): - """Yield tuples of all the resource request fields of this test. - - Yields: - tuple. (requests name, request field) tuples of the test class. - """ - checked_class = cls - while checked_class is not AbstractTest: - for field_name in checked_class.__dict__: - if not field_name.startswith("_"): - field = getattr(checked_class, field_name) - if isinstance(field, BaseResource): - yield (field_name, field) - - checked_class = checked_class.__bases__[0] - @classmethod def get_resource_requests(cls): """Return a list of all the resource requests this test makes. @@ -130,7 +114,7 @@ def get_resource_requests(cls): list. resource requests of the test class. """ all_requests = list(cls.resources) - for (field_name, field) in cls.get_resource_requests_fields(): + for (field_name, field) in get_class_fields(cls, BaseResource): new_request = request(field_name, field.__class__, **field.kwargs) diff --git a/src/rotest/core/block.py b/src/rotest/core/block.py index 94591727..b7b14751 100755 --- a/src/rotest/core/block.py +++ b/src/rotest/core/block.py @@ -2,6 +2,7 @@ # pylint: disable=dangerous-default-value,too-many-arguments from itertools import count +from rotest.common.utils import get_class_fields from rotest.common.config import ROTEST_WORK_DIR from rotest.core.flow_component import (AbstractFlowComponent, MODE_OPTIONAL, MODE_FINALLY, MODE_CRITICAL, @@ -105,40 +106,21 @@ def get_name(cls): @classmethod def get_inputs(cls): - """Return a list of all the input instances of this block. + """Return a dict of all the input instances of this block. Returns: dict. block's inputs (name: input placeholder instance). """ - all_inputs = {} - checked_class = cls - while checked_class is not TestBlock: - all_inputs.update({key: value for (key, value) in - checked_class.__dict__.iteritems() - if (isinstance(value, BlockInput) and - key not in all_inputs)}) - - checked_class = checked_class.__bases__[0] - - return all_inputs + return dict(get_class_fields(cls, BlockInput)) @classmethod def get_outputs(cls): - """Return a list of all the input instances of this block. + """Return a dict of all the input instances of this block. Returns: dict. block's inputs (name: input placeholder instance). """ - all_outputs = {} - checked_class = cls - while checked_class is not TestBlock: - all_outputs.update({key: value for (key, value) in - checked_class.__dict__.iteritems() - if isinstance(value, BlockOutput)}) - - checked_class = checked_class.__bases__[0] - - return all_outputs + return dict(get_class_fields(cls, BlockOutput)) def _share_outputs(self): """Share all the declared outputs of the block.""" diff --git a/src/rotest/management/base_resource.py b/src/rotest/management/base_resource.py index d84230de..7da9b632 100644 --- a/src/rotest/management/base_resource.py +++ b/src/rotest/management/base_resource.py @@ -10,7 +10,7 @@ from attrdict import AttrDict from rotest.common import core_log -from rotest.common.utils import get_work_dir +from rotest.common.utils import get_work_dir, get_class_fields class ConvertToKwargsMeta(type): @@ -95,13 +95,33 @@ def __init__(self, data=None, **kwargs): def create_sub_resources(self): """Create and return the sub resources if needed. + By default, this method searches for sub-resources declared as + class fields, where the 'data' attribute in the declaration points + to the name of the sub-resource's data field under the current's data. + Override and assign sub-resources to fields in the current resource, using the 'data' object. Returns: iterable. sub-resources created. """ - return () + sub_resources = [] + for sub_name, sub_placeholder in get_class_fields(self.__class__, + BaseResource): + + sub_class = sub_placeholder.__class__ + if sub_class.DATA_CLASS is None: + sub_data = None + + else: + sub_data = getattr(self.data, sub_placeholder.data) + + sub_resource = sub_class(data=sub_data, **sub_placeholder.kwargs) + + setattr(self, sub_name, sub_resource) + sub_resources.append(sub_resource) + + return sub_resources def is_available(self, user_name=""): """Return whether resource is available for the given user. From 5121f477b40f983d2aefa73631ebab9b61182599 Mon Sep 17 00:00:00 2001 From: UnDarkle Date: Thu, 13 Sep 2018 10:10:16 +0300 Subject: [PATCH 02/18] added ut --- setup.py | 2 +- src/rotest/core/abstract_test.py | 2 +- tests/management/test_resource_manager.py | 167 +++++++++++++++++++++- 3 files changed, 167 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index d1035ad9..47fc9470 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ """Setup file for handling packaging and distribution.""" from setuptools import setup, find_packages -__version__ = "4.0.1" +__version__ = "4.1.0" result_handlers = [ "db = rotest.core.result.handlers.db_handler:DBHandler", diff --git a/src/rotest/core/abstract_test.py b/src/rotest/core/abstract_test.py index f55f973c..df434bda 100644 --- a/src/rotest/core/abstract_test.py +++ b/src/rotest/core/abstract_test.py @@ -114,7 +114,7 @@ def get_resource_requests(cls): list. resource requests of the test class. """ all_requests = list(cls.resources) - for (field_name, field) in get_class_fields(cls, BaseResource): + for (field_name, field) in dict(get_class_fields(cls, BaseResource)): new_request = request(field_name, field.__class__, **field.kwargs) diff --git a/tests/management/test_resource_manager.py b/tests/management/test_resource_manager.py index 00fd5d79..a4bed097 100644 --- a/tests/management/test_resource_manager.py +++ b/tests/management/test_resource_manager.py @@ -13,6 +13,7 @@ from django.contrib.auth.models import User from swaggapi.api.builder.client import requester +from rotest.management import BaseResource from rotest.management.common.utils import LOCALHOST from rotest.management.client.manager import (ClientResourceManager, ResourceRequest) @@ -573,8 +574,8 @@ def test_lock_release_complex_resource(self): "Sub-resource %r should be locked but " "found available" % sub_resource.name) - resource_instace = DemoResource(data=resources.get()) - self.client._release_resources(resources=[resource_instace]) + resource_instance = DemoResource(data=resources.get()) + self.client._release_resources(resources=[resource_instance]) resources = DemoComplexResourceData.objects.filter( name=self.COMPLEX_NAME) @@ -1260,3 +1261,165 @@ def test_threaded_initialize(self): self.assertEqual(len(ThreadedResource.THREADS), 2, "%d threads were created instead of 2" % len(ThreadedResource.THREADS)) + + def test_lock_alter_complex_resource(self): + """Lock complex resource with the default 'create_sub_resources'. + + * Validates the DB initial state. + * Requests an existing complex resource, using resource client. + * Validates that 1 resource returned. + * Validates the name of the returned resource. + * Validates the type of the returned resource. + * Validates the amount of sub-resources it has. + * Validates the resource and its subs were locked and initialized. + * Releases the locked resource, using resource client. + * Validates the above resource and it sub-resources are now available. + """ + class AlterDemoComplexResource(BaseResource): + """Fake complex resource class, used in resource manager tests.""" + DATA_CLASS = DemoComplexResourceData + demo1 = DemoResource(data='demo1') + demo2 = DemoResource(data='demo2') + + def initialize(self): + """Turns on the initialization flag.""" + super(AlterDemoComplexResource, self).initialize() + self.data.initialization_flag = True + self.data.save() + + resources = DemoComplexResourceData.objects.filter( + name=self.COMPLEX_NAME) + + resources_num = len(resources) + self.assertEqual(resources_num, 1, "Expected 1 complex " + "resource with name %r in DB, found %d" + % (self.COMPLEX_NAME, resources_num)) + + resource, = resources + self.assertTrue(resource.is_available(), "Expected available " + "complex resource with name %r in DB, found %d" + % (self.COMPLEX_NAME, resources_num)) + + request = ResourceRequest('res1', AlterDemoComplexResource, + name=self.COMPLEX_NAME) + + resources = self.client.request_resources(requests=[request]).values() + + resources_num = len(resources) + self.assertEquals(resources_num, 1, "Expected list with 1 " + "resource in it but found %d" % resources_num) + + resource, = resources + self.assertEquals(resource.name, self.COMPLEX_NAME, + "Expected resource with name %r but got %r" + % (self.COMPLEX_NAME, resource.name)) + + self.assertIsInstance(resource, request.type, + "Expected resource of type %r, but got %r" + % (request.type.__name__, + resource.__class__.__name__)) + + self.assertEquals(len(resource.get_sub_resources()), 2, + "Expected to have 2 sub-resources, found %r" + % resource.get_sub_resources()) + + self.assertTrue(resource.data.initialization_flag, + "Resource %r should have been initialized" % + resource.name) + + for sub_resource in resource.get_sub_resources(): + self.assertFalse(sub_resource in AlterDemoComplexResource.__dict__, + "Sub-resource %r is still a placeholder" % + sub_resource.name) + + self.assertFalse(sub_resource.is_available(), + "Sub-resource %r should be locked but " + "found available" % sub_resource.name) + + self.assertIsInstance(sub_resource, DemoResource, + "Expected sub-resource of type %r, got %r" + % (DemoResource.__name__, + sub_resource.__class__.__name__)) + + self.assertTrue(sub_resource.data.initialization_flag, + "Sub-resource %r should have been initialized" % + sub_resource.name) + + resources_data = request.type.DATA_CLASS.objects.filter(~Q(owner=""), + name=self.COMPLEX_NAME) + + resources_num = len(resources_data) + self.assertEquals(resources_num, 1, "Expected 1 locked " + "resource with name %r in DB, found %d" + % (self.COMPLEX_NAME, resources_num)) + + self.client.release_resources(resources=[resource]) + + resources = DemoComplexResourceData.objects.filter( + name=self.COMPLEX_NAME) + + resource, = resources + self.assertTrue(resource.is_available(), "Expected available " + "complex resource with name %r in DB, found %d" + % (self.COMPLEX_NAME, resources_num)) + + def test_lock_alter_complex_service(self): + """Lock complex service with the default 'create_sub_resources'. + + * Validates the DB initial state. + * Requests an existing complex resource, using resource client. + * Validates that 1 resource returned. + * Validates the name of the returned resource. + * Validates the type of the returned resource. + * Validates the amount of sub-resources it has. + * Validates the resource was initialized. + * Validates the above resource and it sub-resources were created. + * Releases the locked resource, using resource client. + """ + class AlterDemoComplexService(BaseResource): + """Fake complex service class, used in resource manager tests.""" + DATA_CLASS = None + demo1 = DemoService() + demo2 = DemoService() + + initialized = False + + def initialize(self): + """Turns on the initialization flag.""" + super(AlterDemoComplexService, self).initialize() + self.initialized = True + + request = ResourceRequest('res1', AlterDemoComplexService, + name=self.COMPLEX_NAME) + + resources = self.client.request_resources(requests=[request]).values() + + resource, = resources + self.assertEquals(resource.name, self.COMPLEX_NAME, + "Expected resource with name %r but got %r" + % (self.COMPLEX_NAME, resource.name)) + + self.assertIsInstance(resource, request.type, + "Expected resource of type %r, but got %r" + % (request.type.__name__, + resource.__class__.__name__)) + + self.assertEquals(len(resource.get_sub_resources()), 2, + "Expected to have 2 sub-resources, found %r" + % resource.get_sub_resources()) + + self.assertTrue(resource.initialized, + "Resource %r should have been initialized" % + resource.name) + + for sub_resource in resource.get_sub_resources(): + self.assertFalse(sub_resource in AlterDemoComplexService.__dict__, + "Sub-resource %r is still a placeholder" % + sub_resource.name) + + self.assertIsInstance(sub_resource, DemoService, + "Expected sub-resource of type %r, got %r" + % (DemoResource.__name__, + sub_resource.__class__.__name__)) + + self.client.release_resources(resources=[resource]) From a6c7f18d5bf67c995444b29b2c8164e07ceba5d8 Mon Sep 17 00:00:00 2001 From: UnDarkle Date: Thu, 13 Sep 2018 10:33:19 +0300 Subject: [PATCH 03/18] fix ut attempt --- src/rotest/core/abstract_test.py | 2 +- src/rotest/management/base_resource.py | 3 ++- tests/management/test_resource_manager.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/rotest/core/abstract_test.py b/src/rotest/core/abstract_test.py index df434bda..f55f973c 100644 --- a/src/rotest/core/abstract_test.py +++ b/src/rotest/core/abstract_test.py @@ -114,7 +114,7 @@ def get_resource_requests(cls): list. resource requests of the test class. """ all_requests = list(cls.resources) - for (field_name, field) in dict(get_class_fields(cls, BaseResource)): + for (field_name, field) in get_class_fields(cls, BaseResource): new_request = request(field_name, field.__class__, **field.kwargs) diff --git a/src/rotest/management/base_resource.py b/src/rotest/management/base_resource.py index 7da9b632..9d5017b9 100644 --- a/src/rotest/management/base_resource.py +++ b/src/rotest/management/base_resource.py @@ -10,6 +10,7 @@ from attrdict import AttrDict from rotest.common import core_log +from rotest.management import ResourceData from rotest.common.utils import get_work_dir, get_class_fields @@ -75,7 +76,7 @@ def __init__(self, data=None, **kwargs): self.logger = core_log self._prev_loggers = [] - if data is not None: + if data is not None and isinstance(data, ResourceData): self.data = data for field_name, field_value in self.data.get_fields().iteritems(): setattr(self, field_name, field_value) diff --git a/tests/management/test_resource_manager.py b/tests/management/test_resource_manager.py index a4bed097..0a015fb0 100644 --- a/tests/management/test_resource_manager.py +++ b/tests/management/test_resource_manager.py @@ -1319,7 +1319,7 @@ def initialize(self): % (request.type.__name__, resource.__class__.__name__)) - self.assertEquals(len(resource.get_sub_resources()), 2, + self.assertEquals(len(list(resource.get_sub_resources())), 2, "Expected to have 2 sub-resources, found %r" % resource.get_sub_resources()) @@ -1404,7 +1404,7 @@ def initialize(self): % (request.type.__name__, resource.__class__.__name__)) - self.assertEquals(len(resource.get_sub_resources()), 2, + self.assertEquals(len(list(resource.get_sub_resources())), 2, "Expected to have 2 sub-resources, found %r" % resource.get_sub_resources()) From d38b665cb8f974c3091b3c7d325e2edbde4e18ab Mon Sep 17 00:00:00 2001 From: UnDarkle Date: Thu, 13 Sep 2018 10:49:56 +0300 Subject: [PATCH 04/18] fix import loop --- src/rotest/management/base_resource.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rotest/management/base_resource.py b/src/rotest/management/base_resource.py index 9d5017b9..7d36f8ca 100644 --- a/src/rotest/management/base_resource.py +++ b/src/rotest/management/base_resource.py @@ -10,8 +10,8 @@ from attrdict import AttrDict from rotest.common import core_log -from rotest.management import ResourceData from rotest.common.utils import get_work_dir, get_class_fields +from rotest.management.models.resource_data import ResourceData class ConvertToKwargsMeta(type): From 6e37a000af9c7b651140b1d48b1972fa4f369bce Mon Sep 17 00:00:00 2001 From: UnDarkle Date: Thu, 13 Sep 2018 11:04:11 +0300 Subject: [PATCH 05/18] fixed ut --- src/rotest/management/base_resource.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rotest/management/base_resource.py b/src/rotest/management/base_resource.py index 7d36f8ca..62711369 100644 --- a/src/rotest/management/base_resource.py +++ b/src/rotest/management/base_resource.py @@ -29,6 +29,7 @@ def __call__(cls, *args, **kwargs): "positional arguments") resource = type.__call__(cls, *args, **kwargs) + kwargs.pop('data') resource.kwargs = kwargs for field_name, field_value in kwargs.iteritems(): setattr(resource, field_name, field_value) From c6bcbdbb990873d218540e6feff3a4b793c3eb5d Mon Sep 17 00:00:00 2001 From: UnDarkle Date: Thu, 13 Sep 2018 11:43:02 +0300 Subject: [PATCH 06/18] really fix ut --- src/rotest/management/base_resource.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rotest/management/base_resource.py b/src/rotest/management/base_resource.py index 62711369..ecbf1c7c 100644 --- a/src/rotest/management/base_resource.py +++ b/src/rotest/management/base_resource.py @@ -29,7 +29,7 @@ def __call__(cls, *args, **kwargs): "positional arguments") resource = type.__call__(cls, *args, **kwargs) - kwargs.pop('data') + kwargs.pop('data', None) resource.kwargs = kwargs for field_name, field_value in kwargs.iteritems(): setattr(resource, field_name, field_value) From 1a18122cb6f8116fcad566b1739f85f7e446776f Mon Sep 17 00:00:00 2001 From: UnDarkle Date: Thu, 13 Sep 2018 12:04:21 +0300 Subject: [PATCH 07/18] try fix ut again --- src/rotest/management/base_resource.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/rotest/management/base_resource.py b/src/rotest/management/base_resource.py index ecbf1c7c..12e56639 100644 --- a/src/rotest/management/base_resource.py +++ b/src/rotest/management/base_resource.py @@ -77,10 +77,11 @@ def __init__(self, data=None, **kwargs): self.logger = core_log self._prev_loggers = [] - if data is not None and isinstance(data, ResourceData): + if data is not None: self.data = data - for field_name, field_value in self.data.get_fields().iteritems(): - setattr(self, field_name, field_value) + if isinstance(data, ResourceData): + for field_name, field_value in data.get_fields().iteritems(): + setattr(self, field_name, field_value) else: self.data = AttrDict() From 4c464c75977240a41516ed239c7bdb5e38d7b7df Mon Sep 17 00:00:00 2001 From: UnDarkle Date: Thu, 13 Sep 2018 12:33:47 +0300 Subject: [PATCH 08/18] fixed get sub data in ut --- tests/management/test_resource_manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/management/test_resource_manager.py b/tests/management/test_resource_manager.py index 0a015fb0..07ee6dd7 100644 --- a/tests/management/test_resource_manager.py +++ b/tests/management/test_resource_manager.py @@ -1332,7 +1332,8 @@ def initialize(self): "Sub-resource %r is still a placeholder" % sub_resource.name) - self.assertFalse(sub_resource.is_available(), + sub_data = DemoResourceData.objects.get(name=sub_resource.name) + self.assertFalse(sub_data.is_available(), "Sub-resource %r should be locked but " "found available" % sub_resource.name) From f991c4042c0d0335c569ba54d642e9f969118965 Mon Sep 17 00:00:00 2001 From: UnDarkle Date: Thu, 13 Sep 2018 12:53:33 +0300 Subject: [PATCH 09/18] final ut fix before review --- tests/management/test_resource_manager.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/management/test_resource_manager.py b/tests/management/test_resource_manager.py index 07ee6dd7..4e63e8cf 100644 --- a/tests/management/test_resource_manager.py +++ b/tests/management/test_resource_manager.py @@ -1356,14 +1356,6 @@ def initialize(self): self.client.release_resources(resources=[resource]) - resources = DemoComplexResourceData.objects.filter( - name=self.COMPLEX_NAME) - - resource, = resources - self.assertTrue(resource.is_available(), "Expected available " - "complex resource with name %r in DB, found %d" - % (self.COMPLEX_NAME, resources_num)) - def test_lock_alter_complex_service(self): """Lock complex service with the default 'create_sub_resources'. From 1f9013420ab342645b6f40dd0fe2d7d7e59a1af3 Mon Sep 17 00:00:00 2001 From: UnDarkle Date: Thu, 20 Sep 2018 12:47:00 +0300 Subject: [PATCH 10/18] used placeholder --- setup.py | 2 +- src/rotest/management/__init__.py | 6 +++--- src/rotest/management/base_resource.py | 18 +++++++++++------- tests/management/test_resource_manager.py | 6 +++--- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/setup.py b/setup.py index 47fc9470..39b2e93e 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ """Setup file for handling packaging and distribution.""" from setuptools import setup, find_packages -__version__ = "4.1.0" +__version__ = "4.2.0" result_handlers = [ "db = rotest.core.result.handlers.db_handler:DBHandler", diff --git a/src/rotest/management/__init__.py b/src/rotest/management/__init__.py index 13ddedd4..40c9374d 100644 --- a/src/rotest/management/__init__.py +++ b/src/rotest/management/__init__.py @@ -1,3 +1,3 @@ -from models import ResourceData -from base_resource import BaseResource -from client.manager import ClientResourceManager +from models import ResourceData +from client.manager import ClientResourceManager +from base_resource import BaseResource, DataPointer diff --git a/src/rotest/management/base_resource.py b/src/rotest/management/base_resource.py index 12e56639..526db896 100644 --- a/src/rotest/management/base_resource.py +++ b/src/rotest/management/base_resource.py @@ -29,7 +29,6 @@ def __call__(cls, *args, **kwargs): "positional arguments") resource = type.__call__(cls, *args, **kwargs) - kwargs.pop('data', None) resource.kwargs = kwargs for field_name, field_value in kwargs.iteritems(): setattr(resource, field_name, field_value) @@ -40,6 +39,12 @@ def __call__(cls, *args, **kwargs): return resource +class DataPointer(object): + """Pointer to a field in the resource's data.""" + def __init__(self, field_name): + self.field_name = field_name + + class BaseResource(object): """Represent the common interface of all the resources. @@ -113,13 +118,12 @@ class fields, where the 'data' attribute in the declaration points BaseResource): sub_class = sub_placeholder.__class__ - if sub_class.DATA_CLASS is None: - sub_data = None - - else: - sub_data = getattr(self.data, sub_placeholder.data) + actual_kwargs = sub_placeholder.kwargs.copy() + for key, value in sub_placeholder.kwargs.iteritems(): + if isinstance(value, DataPointer): + actual_kwargs[key] = getattr(self.data, value.field_name) - sub_resource = sub_class(data=sub_data, **sub_placeholder.kwargs) + sub_resource = sub_class(**actual_kwargs) setattr(self, sub_name, sub_resource) sub_resources.append(sub_resource) diff --git a/tests/management/test_resource_manager.py b/tests/management/test_resource_manager.py index 4e63e8cf..fd1dec9d 100644 --- a/tests/management/test_resource_manager.py +++ b/tests/management/test_resource_manager.py @@ -13,8 +13,8 @@ from django.contrib.auth.models import User from swaggapi.api.builder.client import requester -from rotest.management import BaseResource from rotest.management.common.utils import LOCALHOST +from rotest.management import BaseResource, DataPointer from rotest.management.client.manager import (ClientResourceManager, ResourceRequest) from rotest.management.common.resource_descriptor import \ @@ -1278,8 +1278,8 @@ def test_lock_alter_complex_resource(self): class AlterDemoComplexResource(BaseResource): """Fake complex resource class, used in resource manager tests.""" DATA_CLASS = DemoComplexResourceData - demo1 = DemoResource(data='demo1') - demo2 = DemoResource(data='demo2') + demo1 = DemoResource(data=DataPointer('demo1')) + demo2 = DemoResource(data=DataPointer('demo2')) def initialize(self): """Turns on the initialization flag.""" From d7bebc482f76fcd820f3f0617589ae7ed3d11943 Mon Sep 17 00:00:00 2001 From: UnDarkle Date: Tue, 16 Oct 2018 09:56:52 +0300 Subject: [PATCH 11/18] used metaclass to create placeholders --- src/rotest/management/base_resource.py | 8 +------- src/rotest/management/models/resource_data.py | 18 +++++++++++++++++- tests/management/test_resource_manager.py | 4 ++-- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/rotest/management/base_resource.py b/src/rotest/management/base_resource.py index 526db896..5aabe3f1 100644 --- a/src/rotest/management/base_resource.py +++ b/src/rotest/management/base_resource.py @@ -11,7 +11,7 @@ from rotest.common import core_log from rotest.common.utils import get_work_dir, get_class_fields -from rotest.management.models.resource_data import ResourceData +from rotest.management.models.resource_data import ResourceData, DataPointer class ConvertToKwargsMeta(type): @@ -39,12 +39,6 @@ def __call__(cls, *args, **kwargs): return resource -class DataPointer(object): - """Pointer to a field in the resource's data.""" - def __init__(self, field_name): - self.field_name = field_name - - class BaseResource(object): """Represent the common interface of all the resources. diff --git a/src/rotest/management/models/resource_data.py b/src/rotest/management/models/resource_data.py index b0b19958..7a0d9d6e 100644 --- a/src/rotest/management/models/resource_data.py +++ b/src/rotest/management/models/resource_data.py @@ -9,6 +9,8 @@ from datetime import datetime from django.db import models +from django.utils import six +from django.db.models.base import ModelBase from django.contrib.auth import models as auth_models from django.core.exceptions import ObjectDoesNotExist, ValidationError @@ -17,7 +19,21 @@ from rotest.common.django_utils import get_sub_model, linked_unicode -class ResourceData(models.Model): +class DataPointer(object): + """Pointer to a field in the resource's data.""" + def __init__(self, field_name): + self.field_name = field_name + + +class DataBase(ModelBase): + """Metaclass that creates data pointers for django fields.""" + def add_to_class(cls, name, value): + ModelBase.add_to_class(cls, name, value) + if hasattr(value, 'contribute_to_class'): + setattr(cls, name, DataPointer(name)) + + +class ResourceData(six.with_metaclass(DataBase, models.Model)): """Represent a container for a resource's global data. Inheriting resource datas may add more fields, specific to the resource. diff --git a/tests/management/test_resource_manager.py b/tests/management/test_resource_manager.py index fd1dec9d..f5d33c46 100644 --- a/tests/management/test_resource_manager.py +++ b/tests/management/test_resource_manager.py @@ -1278,8 +1278,8 @@ def test_lock_alter_complex_resource(self): class AlterDemoComplexResource(BaseResource): """Fake complex resource class, used in resource manager tests.""" DATA_CLASS = DemoComplexResourceData - demo1 = DemoResource(data=DataPointer('demo1')) - demo2 = DemoResource(data=DataPointer('demo2')) + demo1 = DemoResource(data=DemoComplexResourceData.demo1) + demo2 = DemoResource(data=DemoComplexResourceData.demo2) def initialize(self): """Turns on the initialization flag.""" From 71d7ff02ae0c61e85baa614ecb0c5b24e35a1450 Mon Sep 17 00:00:00 2001 From: UnDarkle Date: Tue, 16 Oct 2018 10:03:48 +0300 Subject: [PATCH 12/18] fixed by tox --- src/rotest/management/__init__.py | 2 +- tests/management/test_resource_manager.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rotest/management/__init__.py b/src/rotest/management/__init__.py index 40c9374d..c7c31c64 100644 --- a/src/rotest/management/__init__.py +++ b/src/rotest/management/__init__.py @@ -1,3 +1,3 @@ from models import ResourceData +from base_resource import BaseResource from client.manager import ClientResourceManager -from base_resource import BaseResource, DataPointer diff --git a/tests/management/test_resource_manager.py b/tests/management/test_resource_manager.py index f5d33c46..30b49dc6 100644 --- a/tests/management/test_resource_manager.py +++ b/tests/management/test_resource_manager.py @@ -13,8 +13,8 @@ from django.contrib.auth.models import User from swaggapi.api.builder.client import requester +from rotest.management import BaseResource from rotest.management.common.utils import LOCALHOST -from rotest.management import BaseResource, DataPointer from rotest.management.client.manager import (ClientResourceManager, ResourceRequest) from rotest.management.common.resource_descriptor import \ From 7a91c8d5c2887a16cddf5aefa448b133b63d3e59 Mon Sep 17 00:00:00 2001 From: UnDarkle Date: Tue, 16 Oct 2018 10:28:31 +0300 Subject: [PATCH 13/18] fixed by run some --- src/rotest/management/models/resource_data.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/rotest/management/models/resource_data.py b/src/rotest/management/models/resource_data.py index 7a0d9d6e..feb59f31 100644 --- a/src/rotest/management/models/resource_data.py +++ b/src/rotest/management/models/resource_data.py @@ -10,6 +10,7 @@ from django.db import models from django.utils import six +from django.db.models import Field from django.db.models.base import ModelBase from django.contrib.auth import models as auth_models from django.core.exceptions import ObjectDoesNotExist, ValidationError @@ -28,8 +29,9 @@ def __init__(self, field_name): class DataBase(ModelBase): """Metaclass that creates data pointers for django fields.""" def add_to_class(cls, name, value): - ModelBase.add_to_class(cls, name, value) - if hasattr(value, 'contribute_to_class'): + super_add = super(DataBase, cls).add_to_class + super_add(name, value) + if isinstance(value, Field) and hasattr(value, 'contribute_to_class'): setattr(cls, name, DataPointer(name)) From de9adf95ff51f07e478bbe078581b406b9f1eca3 Mon Sep 17 00:00:00 2001 From: UnDarkle Date: Tue, 16 Oct 2018 10:57:20 +0300 Subject: [PATCH 14/18] used class getattr instead --- src/rotest/management/models/resource_data.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/rotest/management/models/resource_data.py b/src/rotest/management/models/resource_data.py index feb59f31..2727653f 100644 --- a/src/rotest/management/models/resource_data.py +++ b/src/rotest/management/models/resource_data.py @@ -6,6 +6,7 @@ # pylint: disable=no-self-use,too-many-public-methods,too-few-public-methods # pylint: disable=attribute-defined-outside-init,invalid-name,old-style-class # pylint: disable=access-member-before-definition,property-on-old-class,no-init +# pylint: disable=no-value-for-parameter from datetime import datetime from django.db import models @@ -28,11 +29,11 @@ def __init__(self, field_name): class DataBase(ModelBase): """Metaclass that creates data pointers for django fields.""" - def add_to_class(cls, name, value): - super_add = super(DataBase, cls).add_to_class - super_add(name, value) - if isinstance(value, Field) and hasattr(value, 'contribute_to_class'): - setattr(cls, name, DataPointer(name)) + def __getattr__(cls, key): + if '_meta' in cls.__dict__ and key in cls._meta.get_field_names(): + return DataPointer(key) + + raise AttributeError(key) class ResourceData(six.with_metaclass(DataBase, models.Model)): From f6dd3e301480a5b9564913612a86580910601b3a Mon Sep 17 00:00:00 2001 From: UnDarkle Date: Tue, 16 Oct 2018 11:02:09 +0300 Subject: [PATCH 15/18] fixed by tox --- src/rotest/management/models/resource_data.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/rotest/management/models/resource_data.py b/src/rotest/management/models/resource_data.py index 2727653f..9a45cada 100644 --- a/src/rotest/management/models/resource_data.py +++ b/src/rotest/management/models/resource_data.py @@ -6,12 +6,10 @@ # pylint: disable=no-self-use,too-many-public-methods,too-few-public-methods # pylint: disable=attribute-defined-outside-init,invalid-name,old-style-class # pylint: disable=access-member-before-definition,property-on-old-class,no-init -# pylint: disable=no-value-for-parameter from datetime import datetime from django.db import models from django.utils import six -from django.db.models import Field from django.db.models.base import ModelBase from django.contrib.auth import models as auth_models from django.core.exceptions import ObjectDoesNotExist, ValidationError From 0cb19e89d00e0296c8e3b521b4142b5bf46f881d Mon Sep 17 00:00:00 2001 From: UnDarkle Date: Tue, 16 Oct 2018 11:37:11 +0300 Subject: [PATCH 16/18] another try --- src/rotest/management/models/resource_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rotest/management/models/resource_data.py b/src/rotest/management/models/resource_data.py index 9a45cada..9f45eba4 100644 --- a/src/rotest/management/models/resource_data.py +++ b/src/rotest/management/models/resource_data.py @@ -28,7 +28,7 @@ def __init__(self, field_name): class DataBase(ModelBase): """Metaclass that creates data pointers for django fields.""" def __getattr__(cls, key): - if '_meta' in cls.__dict__ and key in cls._meta.get_field_names(): + if hasattr(cls, '_meta') and key in cls._meta.get_all_field_names(): return DataPointer(key) raise AttributeError(key) From 65b79535f1c61c281879d34c7de1fa0894a7b335 Mon Sep 17 00:00:00 2001 From: UnDarkle Date: Tue, 16 Oct 2018 12:24:03 +0300 Subject: [PATCH 17/18] should be working now --- src/rotest/management/base_resource.py | 7 ++++++- src/rotest/management/models/__init__.py | 20 +++++++++---------- src/rotest/management/models/resource_data.py | 4 +++- tests/management/test_resource_manager.py | 9 +++++++-- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/rotest/management/base_resource.py b/src/rotest/management/base_resource.py index 5aabe3f1..4ff60852 100644 --- a/src/rotest/management/base_resource.py +++ b/src/rotest/management/base_resource.py @@ -8,6 +8,8 @@ from ipdbugger import debug from attrdict import AttrDict +from django.db.models.fields.related import \ + ReverseSingleRelatedObjectDescriptor from rotest.common import core_log from rotest.common.utils import get_work_dir, get_class_fields @@ -114,7 +116,10 @@ class fields, where the 'data' attribute in the declaration points sub_class = sub_placeholder.__class__ actual_kwargs = sub_placeholder.kwargs.copy() for key, value in sub_placeholder.kwargs.iteritems(): - if isinstance(value, DataPointer): + if isinstance(value, ReverseSingleRelatedObjectDescriptor): + actual_kwargs[key] = getattr(self.data, value.field.name) + + elif isinstance(value, DataPointer): actual_kwargs[key] = getattr(self.data, value.field_name) sub_resource = sub_class(**actual_kwargs) diff --git a/src/rotest/management/models/__init__.py b/src/rotest/management/models/__init__.py index 30489ad9..e3f4b39b 100644 --- a/src/rotest/management/models/__init__.py +++ b/src/rotest/management/models/__init__.py @@ -1,10 +1,10 @@ -"""Define Rotest's common models. - -The Django infrastructure expects a models.py file containing all the models -definitions for each application. This folder is a workaround used in order -to separate the different common application models into different files. -""" -# pylint: disable=unused-import -from rotest.management.models.resource_data import ResourceData -from rotest.management.models.ut_models import \ - DemoResourceData, DemoComplexResourceData +"""Define Rotest's common models. + +The Django infrastructure expects a models.py file containing all the models +definitions for each application. This folder is a workaround used in order +to separate the different common application models into different files. +""" +# pylint: disable=unused-import +from rotest.management.models.resource_data import ResourceData, DataPointer +from rotest.management.models.ut_models import \ + DemoResourceData, DemoComplexResourceData diff --git a/src/rotest/management/models/resource_data.py b/src/rotest/management/models/resource_data.py index 9f45eba4..d19a6a1f 100644 --- a/src/rotest/management/models/resource_data.py +++ b/src/rotest/management/models/resource_data.py @@ -28,7 +28,9 @@ def __init__(self, field_name): class DataBase(ModelBase): """Metaclass that creates data pointers for django fields.""" def __getattr__(cls, key): - if hasattr(cls, '_meta') and key in cls._meta.get_all_field_names(): + if hasattr(cls, '_meta') and \ + key in (field.name for field in cls._meta.fields): + return DataPointer(key) raise AttributeError(key) diff --git a/tests/management/test_resource_manager.py b/tests/management/test_resource_manager.py index 30b49dc6..f935cec0 100644 --- a/tests/management/test_resource_manager.py +++ b/tests/management/test_resource_manager.py @@ -1280,6 +1280,7 @@ class AlterDemoComplexResource(BaseResource): DATA_CLASS = DemoComplexResourceData demo1 = DemoResource(data=DemoComplexResourceData.demo1) demo2 = DemoResource(data=DemoComplexResourceData.demo2) + demo3 = DemoService(name=DemoComplexResourceData.name) def initialize(self): """Turns on the initialization flag.""" @@ -1314,13 +1315,17 @@ def initialize(self): "Expected resource with name %r but got %r" % (self.COMPLEX_NAME, resource.name)) + self.assertEquals(resource.name, resource.demo3.name, + "Expected sub-service with name %r but got %r" + % (resource.name, resource.demo3.name)) + self.assertIsInstance(resource, request.type, "Expected resource of type %r, but got %r" % (request.type.__name__, resource.__class__.__name__)) - self.assertEquals(len(list(resource.get_sub_resources())), 2, - "Expected to have 2 sub-resources, found %r" + self.assertEquals(len(list(resource.get_sub_resources())), 3, + "Expected to have 3 sub-resources, found %r" % resource.get_sub_resources()) self.assertTrue(resource.data.initialization_flag, From 20530e2920997d42ef341dd34c193281169d893a Mon Sep 17 00:00:00 2001 From: UnDarkle Date: Tue, 16 Oct 2018 12:27:17 +0300 Subject: [PATCH 18/18] fixed up ut a bit --- src/rotest/management/__init__.py | 2 +- tests/management/test_resource_manager.py | 20 ++++++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/rotest/management/__init__.py b/src/rotest/management/__init__.py index c7c31c64..94127927 100644 --- a/src/rotest/management/__init__.py +++ b/src/rotest/management/__init__.py @@ -1,3 +1,3 @@ -from models import ResourceData from base_resource import BaseResource +from models import ResourceData, DataPointer from client.manager import ClientResourceManager diff --git a/tests/management/test_resource_manager.py b/tests/management/test_resource_manager.py index f935cec0..68c04851 100644 --- a/tests/management/test_resource_manager.py +++ b/tests/management/test_resource_manager.py @@ -13,7 +13,7 @@ from django.contrib.auth.models import User from swaggapi.api.builder.client import requester -from rotest.management import BaseResource +from rotest.management import BaseResource, DataPointer from rotest.management.common.utils import LOCALHOST from rotest.management.client.manager import (ClientResourceManager, ResourceRequest) @@ -1315,10 +1315,6 @@ def initialize(self): "Expected resource with name %r but got %r" % (self.COMPLEX_NAME, resource.name)) - self.assertEquals(resource.name, resource.demo3.name, - "Expected sub-service with name %r but got %r" - % (resource.name, resource.demo3.name)) - self.assertIsInstance(resource, request.type, "Expected resource of type %r, but got %r" % (request.type.__name__, @@ -1328,6 +1324,10 @@ def initialize(self): "Expected to have 3 sub-resources, found %r" % resource.get_sub_resources()) + self.assertEquals(resource.name, resource.demo3.name, + "Expected sub-service with name %r but got %r" + % (resource.name, resource.demo3.name)) + self.assertTrue(resource.data.initialization_flag, "Resource %r should have been initialized" % resource.name) @@ -1378,7 +1378,7 @@ class AlterDemoComplexService(BaseResource): """Fake complex service class, used in resource manager tests.""" DATA_CLASS = None demo1 = DemoService() - demo2 = DemoService() + demo2 = DemoService(name=DataPointer('name')) initialized = False @@ -1406,6 +1406,14 @@ def initialize(self): "Expected to have 2 sub-resources, found %r" % resource.get_sub_resources()) + self.assertEquals(resource.name, resource.demo2.name, + "Expected sub-service with name %r but got %r" + % (resource.name, resource.demo2.name)) + + self.assertNotEquals(resource.name, resource.demo1.name, + "Expected sub-service with name different than %r" + % (resource.name, resource.demo1.name)) + self.assertTrue(resource.initialized, "Resource %r should have been initialized" % resource.name)