From f73236bfeb329cd875fb2b673e3845af544cf9e5 Mon Sep 17 00:00:00 2001 From: Eli Kogan-Wang Date: Tue, 3 Sep 2024 11:08:48 +0200 Subject: [PATCH 1/4] Rename Component.instances to Component._instances to prevent collisions --- src/batou/component.py | 4 ++-- src/batou/environment.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/batou/component.py b/src/batou/component.py index 0e4548bd..de6adf57 100644 --- a/src/batou/component.py +++ b/src/batou/component.py @@ -159,7 +159,7 @@ class Component(object): #: After the configuration phase, this list is checked for #: components that have component._prepared == False and #: warns about them. - instances: List["Component"] = [] + _instances: List["Component"] = [] @property def defdir(self): @@ -229,7 +229,7 @@ def __init__(self, namevar=None, **kw): self._init_breadcrumbs = init_breadcrumbs - Component.instances.append(self) + Component._instances.append(self) self.timer = batou.utils.Timer(self.__class__.__name__) # Are any keyword arguments undefined attributes? # This is a somewhat rough implementation as it allows overriding diff --git a/src/batou/environment.py b/src/batou/environment.py index 0b8477de..68cc200a 100644 --- a/src/batou/environment.py +++ b/src/batou/environment.py @@ -498,7 +498,7 @@ def configure(self): for root in working_set: try: - Component.instances.clear() + Component._instances.clear() self.resources.reset_component_resources(root) root.overrides = self.overrides.get(root.name, {}) root.prepare() @@ -519,7 +519,7 @@ def configure(self): ) else: unprepared_components = [] - for component in Component.instances: + for component in Component._instances: if not component._prepared: unprepared_components.append(component) if unprepared_components: From b51b76131c3b3d17f000733e3d0fca1e3476e286 Mon Sep 17 00:00:00 2001 From: Eli Kogan-Wang Date: Tue, 3 Sep 2024 11:25:38 +0200 Subject: [PATCH 2/4] Add changelog: .instances renamed to ._instances --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 4d65d94a..a79be2c3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,6 +16,8 @@ context. This variable contains a comment block that signals that the file content is generated by batou. (#356) - Development: update Sphinx to 5.3.0 to fix readthedocs build failures. +- Component object model: Class variable .instances renamed to ._instances + to avoid collisions with user-applications. ## 2.5.0b3 (2024-08-05) ----------------------- From fb7e9cf2626db135acd65d28c82147a1223a2262 Mon Sep 17 00:00:00 2001 From: Eli Kogan-Wang Date: Tue, 3 Sep 2024 11:19:10 +0200 Subject: [PATCH 3/4] Add warning: If component has update() but no verify() --- src/batou/__init__.py | 35 +++++++++++++++++++++++++++++++++++ src/batou/environment.py | 25 +++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/src/batou/__init__.py b/src/batou/__init__.py index 62636c97..5b0743ed 100644 --- a/src/batou/__init__.py +++ b/src/batou/__init__.py @@ -521,6 +521,41 @@ def report(self): output.tabular("Root", self.root_name, red=True) +class ComponentWithUpdateWithoutVerify(ConfigurationError): + """Some components have an update method but no verify method.""" + + sort_key = (5, "without_verify") + + @classmethod + def from_context(cls, components, root): + self = cls() + self.components = [] + for component in components: + self.components.append(repr(component.__class__.__name__)) + self.root_name = root.name + return self + + def __str__(self): + out_str = "Some components have an update method but no verify method:" + for component in self.components: + out_str += f"\n {component}" + out_str += f"\nRoot: {self.root_name}" + out_str += f"\nThe update() method may not be called by batou if the verify() method is missing." + return out_str + + def report(self): + output.error( + f"Some components have an update method but no verify method:" + ) + for component in self.components: + output.line(f" {component}", red=True) + output.tabular("Root", self.root_name, red=True) + output.line( + f"The update() method may not be called by batou if the verify() method is missing.", + red=True, + ) + + class UnsatisfiedResources(ConfigurationError): """Some required resources were never provided.""" diff --git a/src/batou/environment.py b/src/batou/environment.py index 68cc200a..92ba5969 100644 --- a/src/batou/environment.py +++ b/src/batou/environment.py @@ -15,6 +15,7 @@ import batou.vfs from batou import ( ComponentLoadingError, + ComponentWithUpdateWithoutVerify, ConfigurationError, CycleErrorDetected, DuplicateHostError, @@ -518,6 +519,8 @@ def configure(self): ) ) else: + # warnings: does not fail the deployment + # 1. unprepared component warning unprepared_components = [] for component in Component._instances: if not component._prepared: @@ -531,6 +534,28 @@ def configure(self): ) # exceptions.append(unused_exception) output.warn(str(unused_exception)) + + # 2. a component has .update() but no .verify() + components_without_verify = [] + + def has_original_update_method(component): + return type(component).update != Component.update + + def has_original_verify_method(component): + return type(component).verify != Component.verify + + for component in Component._instances: + if not has_original_update_method( + component + ) and has_original_verify_method(component): + components_without_verify.append(component) + if components_without_verify: + component_without_verify_exception = ( + ComponentWithUpdateWithoutVerify.from_context( + components_without_verify, root + ) + ) + output.warn(str(component_without_verify_exception)) # configured this component successfully # we won't have to retry it later continue From faeea2427b3784012c7d5aa5e3cc7e22835d4ca4 Mon Sep 17 00:00:00 2001 From: Eli Kogan-Wang Date: Tue, 10 Sep 2024 10:58:20 +0200 Subject: [PATCH 4/4] fix behaviour of warning --- src/batou/__init__.py | 10 ++++++---- src/batou/environment.py | 33 ++++++++++++++++++--------------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/batou/__init__.py b/src/batou/__init__.py index 5b0743ed..c79faf3f 100644 --- a/src/batou/__init__.py +++ b/src/batou/__init__.py @@ -527,19 +527,21 @@ class ComponentWithUpdateWithoutVerify(ConfigurationError): sort_key = (5, "without_verify") @classmethod - def from_context(cls, components, root): + def from_context(cls, components, roots): self = cls() self.components = [] for component in components: self.components.append(repr(component.__class__.__name__)) - self.root_name = root.name + self.roots = [] + for root in roots: + self.roots.append(root.name) return self def __str__(self): out_str = "Some components have an update method but no verify method:" - for component in self.components: + for idx, component in enumerate(self.components): out_str += f"\n {component}" - out_str += f"\nRoot: {self.root_name}" + out_str += f"\nRoot: {self.roots[idx]}" out_str += f"\nThe update() method may not be called by batou if the verify() method is missing." return out_str diff --git a/src/batou/environment.py b/src/batou/environment.py index 92ba5969..611a8af6 100644 --- a/src/batou/environment.py +++ b/src/batou/environment.py @@ -493,6 +493,7 @@ def configure(self): while working_set: exceptions = [] + components_without_verify = [] previous_working_sets.append(working_set.copy()) retry = set() self.resources.dirty_dependencies.clear() @@ -536,26 +537,19 @@ def configure(self): output.warn(str(unused_exception)) # 2. a component has .update() but no .verify() - components_without_verify = [] - def has_original_update_method(component): - return type(component).update != Component.update + return type(component).update == Component.update def has_original_verify_method(component): - return type(component).verify != Component.verify + return type(component).verify == Component.verify for component in Component._instances: - if not has_original_update_method( - component - ) and has_original_verify_method(component): - components_without_verify.append(component) - if components_without_verify: - component_without_verify_exception = ( - ComponentWithUpdateWithoutVerify.from_context( - components_without_verify, root - ) - ) - output.warn(str(component_without_verify_exception)) + if ( + not has_original_update_method(component) + and has_original_verify_method(component) + and (component not in components_without_verify) + ): + components_without_verify.append((component, root)) # configured this component successfully # we won't have to retry it later continue @@ -598,6 +592,15 @@ def has_original_verify_method(component): working_set = retry + # warn if a component has .update() but no .verify() + if components_without_verify: + component_without_verify_exception = ( + ComponentWithUpdateWithoutVerify.from_context( + components_without_verify, root + ) + ) + output.warn(str(component_without_verify_exception)) + # We managed to converge on a working set. However, some resource were # provided but never used. We're rather picky here and report this as # an error.