diff --git a/product/ERP5/ERP5Site.py b/product/ERP5/ERP5Site.py index d950cd89221b2b28d6d91caa705376dc0e828e7d..165b854e3363bed855d57d8da30b0d6b99e15df9 100644 --- a/product/ERP5/ERP5Site.py +++ b/product/ERP5/ERP5Site.py @@ -528,6 +528,18 @@ class ERP5Site(ResponseHeaderGenerator, FolderMixIn, CMFSite, CacheCookieMixin): XXX-arnau: must be written through an interaction workflow when ERP5Site will become a real ERP5 object... """ + # Clear completely astroid cache: could be more efficient to clear only + # the deleted versions and all their associated ZODB Components packages, + # but it's probably enough as this is not done often after all... + try: + from astroid.builder import MANAGER + except ImportError: + pass + else: + for k in MANAGER.astroid_cache.keys(): + if k.startswith('erp5.component.'): + del MANAGER.astroid_cache[k] + if not isinstance(version_priority_tuple, tuple): version_priority_tuple = tuple(version_priority_tuple) diff --git a/product/ERP5/bootstrap/erp5_core/WorkflowTemplateItem/portal_workflow/dynamic_class_generation_interaction_workflow/scripts/ComponentTool_reset.py b/product/ERP5/bootstrap/erp5_core/WorkflowTemplateItem/portal_workflow/dynamic_class_generation_interaction_workflow/scripts/ComponentTool_reset.py index 3ccf0896f7ce26cb2605a84d11130565fced7ed5..7e32c93436239fb4ead28bf580db57bbaddce61d 100644 --- a/product/ERP5/bootstrap/erp5_core/WorkflowTemplateItem/portal_workflow/dynamic_class_generation_interaction_workflow/scripts/ComponentTool_reset.py +++ b/product/ERP5/bootstrap/erp5_core/WorkflowTemplateItem/portal_workflow/dynamic_class_generation_interaction_workflow/scripts/ComponentTool_reset.py @@ -1 +1,2 @@ +state_change['object'].reset() state_change['object'].getPortalObject().portal_components.resetOnceAtTransactionBoundary() diff --git a/product/ERP5Type/dynamic/component_package.py b/product/ERP5Type/dynamic/component_package.py index d9b6e54e8d9b11c25b13144e6e236819c24a90f8..9352c71181454df9d637c784a56077b1f89dc52b 100644 --- a/product/ERP5Type/dynamic/component_package.py +++ b/product/ERP5Type/dynamic/component_package.py @@ -429,14 +429,6 @@ class ComponentDynamicPackage(ModuleType): delattr(package, name) - # Clear pylint cache - try: - from astroid.builder import MANAGER - except ImportError: - pass - else: - MANAGER.astroid_cache.pop(module_name, None) - class ToolComponentDynamicPackage(ComponentDynamicPackage): def reset(self, *args, **kw): """ diff --git a/product/ERP5Type/mixin/component.py b/product/ERP5Type/mixin/component.py index fe67f7e2159c9426238170218120e1cfbc694c96..49246938b33ec782c0be714209a8add45bbbb8c5 100644 --- a/product/ERP5Type/mixin/component.py +++ b/product/ERP5Type/mixin/component.py @@ -311,6 +311,30 @@ class ComponentMixin(PropertyRecordableMixin, Base): """ return checkPythonSourceCode(self.getTextContent(), self.getPortalType()) + security.declareProtected(Permissions.ResetDynamicClasses, 'reset') + def reset(self): + """ + Called on validate/invalidate + """ + # Clear pylint cache + try: + from astroid.builder import MANAGER + except ImportError: + pass + else: + namespace = self._getDynamicModuleNamespace() + reference = self.getReference() + # Clear the main module (erp5.component.document.foo_version.Bar) + version_module_name = '%s.%s_version.%s' % (namespace, + self.getVersion(), + reference) + module_name = '%s.%s' % (namespace, reference) + version_astroid_module = MANAGER.astroid_cache.pop(version_module_name, None) + # And its alias (erp5.component.document.Bar) + if (version_astroid_module is not None and + MANAGER.astroid_cache.get(module_name) is version_astroid_module): + del MANAGER.astroid_cache[module_name] + security.declareProtected(Permissions.ModifyPortalContent, 'PUT') def PUT(self, REQUEST, RESPONSE): """ diff --git a/product/ERP5Type/tests/testDynamicClassGeneration.py b/product/ERP5Type/tests/testDynamicClassGeneration.py index 224c21beb1e81a497ccdd34dbb6ed68eae12502b..6484747322595a358c6e26e6327d54baddb18da1 100644 --- a/product/ERP5Type/tests/testDynamicClassGeneration.py +++ b/product/ERP5Type/tests/testDynamicClassGeneration.py @@ -2206,8 +2206,7 @@ def hoge(): imported_module2_with_version = self._getComponentFullModuleName( imported_reference2, version='erp5') - component.setTextContent( - """# -*- coding: utf-8 -*- + component_text_content = ("""# -*- coding: utf-8 -*- # Source code with non-ASCII character should not fail: éà ホゲ from %(namespace)s import %(reference1)s from %(namespace)s.erp5_version import %(reference1)s @@ -2242,6 +2241,7 @@ from Shared.DC.ZRDB.Results import Results # pylint: disable=unused-import module2=imported_module2, module2_with_version=imported_module2_with_version)) + component.getTextContent()) + component.setTextContent(component_text_content) self.tic() self._assertAstroidCacheContent( must_be_in_cache_set={'%s' % namespace, @@ -2324,6 +2324,101 @@ undefined() imported_module2_with_version]) self.assertEqual(component.getTextContentWarningMessageList(), []) + # Check that astroid cache is properly cleaned up when a Component is modified + component.setTextContent(component_text_content) + self.tic() + self.assertEqual(component.getValidationState(), 'validated') + self.assertEqual(component.getTextContentErrorMessageList(), []) + self.assertEqual(component.getTextContentWarningMessageList(), []) + + imported_component2.setTextContent(imported_component2.getTextContent() + '\n') + self.tic() + self.assertEqual(imported_component2.getValidationState(), 'validated') + self.assertEqual(imported_component2.getTextContentErrorMessageList(), []) + self.assertEqual(imported_component2.getTextContentWarningMessageList(), []) + self._assertAstroidCacheContent( + must_be_in_cache_set={'%s' % namespace, + '%s.erp5_version' % namespace, + imported_module1, + imported_module1_with_version}, + must_not_be_in_cache_set={imported_module2, + imported_module2_with_version}) + + imported_component2.invalidate() + self.tic() + self.assertEqual(imported_component2.getValidationState(), 'invalidated') + self._assertAstroidCacheContent( + must_be_in_cache_set={'%s' % namespace, + '%s.erp5_version' % namespace, + imported_module1, + imported_module1_with_version}, + must_not_be_in_cache_set={imported_module2, + imported_module2_with_version}) + + imported_component2.validate() + self.tic() + self.assertEqual(imported_component2.getValidationState(), 'validated') + self.assertEqual(imported_component2.getTextContentErrorMessageList(), []) + self.assertEqual(imported_component2.getTextContentWarningMessageList(), []) + + # And when version_priority is changed + priority_tuple = self.portal.getVersionPriorityList() + imported_module2_with_bar_version = self._getComponentFullModuleName( + imported_reference2, version='bar') + try: + self.portal.setVersionPriorityList(('bar | 42.0',) + priority_tuple) + self.tic() + self._assertAstroidCacheContent( + must_be_in_cache_set=set(), + must_not_be_in_cache_set={'%s' % namespace, + '%s.erp5_version' % namespace, + '%s.bar_version' % namespace, + imported_module1, + imported_module1_with_version, + imported_module2, + imported_module2_with_version, + imported_module2_with_bar_version, + '%s.bar_version' % namespace}) + + imported_component2.setId(imported_component2.getId().replace('erp5', 'bar')) + imported_component2.setVersion('bar') + self.tic() + self.assertEqual(imported_component2.getValidationState(), 'validated') + self.assertEqual(imported_component2.getTextContentErrorMessageList(), []) + self.assertEqual(imported_component2.getTextContentWarningMessageList(), []) + + component.setTextContent(component.getTextContent().replace( + imported_module2_with_version, imported_module2_with_bar_version)) + self.tic() + self.assertEqual(imported_component2.getValidationState(), 'validated') + self.assertEqual(imported_component2.getTextContentErrorMessageList(), []) + self.assertEqual(imported_component2.getTextContentWarningMessageList(), []) + self._assertAstroidCacheContent( + must_be_in_cache_set={'%s' % namespace, + '%s.erp5_version' % namespace, + '%s.bar_version' % namespace, + imported_module1, + imported_module1_with_version, + imported_module2, + imported_module2_with_bar_version, + '%s.bar_version' % namespace}, + must_not_be_in_cache_set={imported_module2_with_version}) + + finally: + self.portal.setVersionPriorityList(priority_tuple) + self.tic() + self._assertAstroidCacheContent( + must_be_in_cache_set=set(), + must_not_be_in_cache_set={'%s' % namespace, + '%s.erp5_version' % namespace, + '%s.bar_version' % namespace, + imported_module1, + imported_module1_with_version, + imported_module2, + imported_module2_with_version, + imported_module2_with_bar_version, + '%s.bar_version' % namespace}) + def testPylintAstroidModuleGeneratedOnce(self): imported_reference = self._generateReference('TestPylintAstroidModuleGeneratedOnceImported') imported_component = self._newComponent(imported_reference)