From 9d46562419f8d32c2a8bdadea6885c959143ec23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Steinhagen?= Date: Mon, 10 Feb 2025 08:30:15 +0100 Subject: [PATCH] docs: class and method docs --- main.py | 5 ++- src/aggregators/carbon_totals.py | 23 +++++++++++- src/interfaces/compliance_checker.py | 7 +++- src/interfaces/logger.py | 34 ++++++++++++++--- src/interfaces/material_processor.py | 10 ++++- src/interfaces/model_processor.py | 19 ++++++++-- src/interfaces/validator.py | 12 +++++- src/logging/compliance_logger.py | 14 +++---- src/processors/compliance.py | 3 ++ src/processors/material.py | 7 +++- src/processors/model.py | 56 ++++++++++++++++++++++------ src/utils/constants.py | 2 + src/validators/validators.py | 0 13 files changed, 155 insertions(+), 37 deletions(-) delete mode 100644 src/validators/validators.py diff --git a/main.py b/main.py index 688eeeb..bbf698a 100644 --- a/main.py +++ b/main.py @@ -12,9 +12,10 @@ from src.validators.revit import RevitSourceValidator from src.aggregators.carbon_totals import MassAggregator from src.logging.compliance_logger import ComplianceLogger - +# TODO: Function inputs class FunctionInputs(AutomateBase): - # An example of how to use secret values. + """User-defined function inputs. + """ whisper_message: SecretStr = Field(title="This is a secret message") forbidden_speckle_type: str = Field( title="Forbidden speckle type", diff --git a/src/aggregators/carbon_totals.py b/src/aggregators/carbon_totals.py index 1daa10b..8c0ad64 100644 --- a/src/aggregators/carbon_totals.py +++ b/src/aggregators/carbon_totals.py @@ -2,16 +2,35 @@ from typing import Dict from collections import defaultdict +# TODO: Replace with carbon aggregator when ready class MassAggregator: + """Cumulative sum of the computed masses. Grouped by level and type. + """ def __init__(self): self.totals = defaultdict(lambda: defaultdict(lambda: defaultdict(float))) - def add_mass(self, mass: float, level: str, type: str, material: str) -> None: + # TODO: Check! Changes for Revit model groups probably broke this + def add_mass(self, mass: float, level: str, collection_type: str, material: str) -> None: + """Adds computed mass of a single object to the totals. + Ignores computed masses < 1e-6. + + Args: + mass (float): computed mass + level (str): string of the associated level of the object + type (str): object collection (e.g. "Columns", "Structural Foundations") + material (str): name of the structural asset + """ if mass <= 1e-6: return - self.totals[level][type][material] += mass + self.totals[level][collection_type][material] += mass + # TODO: Check! Changes for Revit model groups probably broke this def get_totals(self) -> Dict: + """Return computed totals in a structured dictionary. + + Returns: + Dict: nested "by_level" and then "by_type" + """ return { "by_level": { level: { diff --git a/src/interfaces/compliance_checker.py b/src/interfaces/compliance_checker.py index 3fd80ef..3e9476c 100644 --- a/src/interfaces/compliance_checker.py +++ b/src/interfaces/compliance_checker.py @@ -2,8 +2,13 @@ from abc import ABC, abstractmethod from typing import Any, List +# TODO: Passed around but never used class ComplianceChecker(ABC): + """Interface for compliance checks. + Compliance are intended to be called for every object where an attribute is assumed. + """ + # TODO: Passed around but never used @abstractmethod def check_compliance(self, element: Any, required_properties: List[str]) -> bool: - """Check if element meets compliance requirements""" + """Check if element contains attribute(s)""" pass diff --git a/src/interfaces/logger.py b/src/interfaces/logger.py index 5991fd7..849e0be 100644 --- a/src/interfaces/logger.py +++ b/src/interfaces/logger.py @@ -1,29 +1,51 @@ from abc import ABC, abstractmethod from typing import Dict, List - +# TODO: Implementations use **kwargs which is silly. Formalize. class Logger(ABC): + """Interface for logging. + """ @abstractmethod def log_error(self, message: str, **kwargs) -> None: - """Log an error message""" + """Log an error. + + Args: + message (str): error message + """ pass @abstractmethod def log_warning(self, message: str, **kwargs) -> None: - """Log a warning message""" + """Log a warning. + + Args: + message (str): warning message + """ pass @abstractmethod def log_success(self, object_id: str, **kwargs) -> None: - """Log a success message""" + """Log a success. + + Args: + object_id (str): success message + """ pass @abstractmethod def get_warnings_summary(self) -> Dict: - """Get summary of logged warning messages""" + """Returns a dictionary of warning messages. The dictionary groups the wanring types. + + Returns: + Dict: {warning_type : [object_ids], ...} + """ pass @abstractmethod def get_successful_summary(self) -> List: - """Get list of successfully processed elements""" + """Returns a list of all successfully processed elements. + + Returns: + List: [object_ids] + """ pass diff --git a/src/interfaces/material_processor.py b/src/interfaces/material_processor.py index 5ae4701..7c02676 100644 --- a/src/interfaces/material_processor.py +++ b/src/interfaces/material_processor.py @@ -3,9 +3,17 @@ from typing import Dict, Any class MaterialProcessor(ABC): + """Interface for processing a material. + """ @abstractmethod def process_material( self, material_data: Dict[str, Any], level: str, type_name: str ) -> None: - """Process material data and compute required properties""" + """Process material data and compute required properties + + Args: + material_data (Dict[str, Any]): material data + level (str): associated level of object + type_name (str): object type + """ pass diff --git a/src/interfaces/model_processor.py b/src/interfaces/model_processor.py index 9c76e67..de50478 100644 --- a/src/interfaces/model_processor.py +++ b/src/interfaces/model_processor.py @@ -3,12 +3,25 @@ from typing import Any class ModelProcessor(ABC): + """Interface for model processing. + """ @abstractmethod def process_elements(self, model: Any) -> None: - """Process all elements in the model""" + """Process all elements in the model. + + Args: + model (Any): root commit + """ pass + # TODO: element should be Base? @abstractmethod - def process_element(self, level: str, type_name: str, element: Any) -> None: - """Process a single element""" + def process_element(self, level: str, type_name: str, model_object: Any) -> None: + """Process a single element. + + Args: + level (str): associated level of object + type_name (str): object type + model_object (Any): speckle object + """ pass diff --git a/src/interfaces/validator.py b/src/interfaces/validator.py index f720def..5812d5f 100644 --- a/src/interfaces/validator.py +++ b/src/interfaces/validator.py @@ -2,7 +2,17 @@ from abc import ABC, abstractmethod class SourceApplicationValidator(ABC): + """Interface for source application validator. + Host app should be supported by the automation. + """ @abstractmethod def validate(self, source_app: str) -> bool: - """Validate if the source application is supported""" + """Assert that the source application is supported. + + Args: + source_app (str): sourceApplication from the commit root + + Returns: + bool: True if supported, False if not + """ pass diff --git a/src/logging/compliance_logger.py b/src/logging/compliance_logger.py index eb45ab1..c3cdad6 100644 --- a/src/logging/compliance_logger.py +++ b/src/logging/compliance_logger.py @@ -4,21 +4,22 @@ from collections import defaultdict from src.interfaces.logger import Logger # Import the interface +# NOTE: Only provide docstring if not covered by base class -class ComplianceLogger(Logger): # Explicitly implement Logger interface +class ComplianceLogger(Logger): + """Implements Logger interface + """ def __init__(self): self.missing_properties: DefaultDict[str, set] = defaultdict(set) self.successful_elements: set = set() self._structlog = structlog.get_logger() def log_error(self, message: str, **kwargs) -> None: - """Log an error message""" self._structlog.error(message, **kwargs) def log_warning(self, message: str, **kwargs) -> None: - """ - Log a warning message. Also tracks missing properties if they are specified - in the kwargs. + """ Log a warning message. + Categorises and caches missing properties if 'missing_property' and 'object_id' specified in the kwargs. Args: message: Warning message to log @@ -34,16 +35,13 @@ class ComplianceLogger(Logger): # Explicitly implement Logger interface self.missing_properties[missing_property].add(object_id) def log_success(self, object_id: str) -> None: - """Log a successful element processing""" self._structlog.info(f"Successfully processed element", object_id=object_id) self.successful_elements.add(object_id) def get_warnings_summary(self) -> Dict[str, list]: - """Get summary of logged messages""" return { prop: list(elements) for prop, elements in self.missing_properties.items() } def get_successful_summary(self) -> list: - """Get list of successfully processed elements""" return list(self.successful_elements) diff --git a/src/processors/compliance.py b/src/processors/compliance.py index 58d5dfc..a864648 100644 --- a/src/processors/compliance.py +++ b/src/processors/compliance.py @@ -3,8 +3,11 @@ from src.interfaces.compliance_checker import ComplianceChecker from src.interfaces.logger import Logger from src.utils.constants import ID +# NOTE: Only provide docstring if not covered by base class class RevitComplianceChecker(ComplianceChecker): + """Implementation of the ComplianceChecker in the context of Revit. + """ def __init__(self, logger: Logger): self._logger = logger diff --git a/src/processors/material.py b/src/processors/material.py index d4524b0..fc66a09 100644 --- a/src/processors/material.py +++ b/src/processors/material.py @@ -2,11 +2,14 @@ from typing import Dict, Any from src.interfaces.material_processor import MaterialProcessor from src.models.material import ( MaterialData, -) # Import from models instead of defining here -from src.utils.constants import * +) +from src.utils.constants import VOLUME, VALUE, DENSITY, STRUCTURAL_ASSET +# NOTE: Only provide docstring if not covered by base class class RevitMaterialProcessor(MaterialProcessor): + """Implementation of the MaterialProcessor for the Revit context. + """ def __init__(self, mass_aggregator: "MassAggregator"): self._mass_aggregator = mass_aggregator diff --git a/src/processors/model.py b/src/processors/model.py index a039050..0421414 100644 --- a/src/processors/model.py +++ b/src/processors/model.py @@ -18,8 +18,12 @@ from src.utils.constants import ( DENSITY, ) +# NOTE: Only provide docstring if not covered by base class + class RevitModelProcessor(ModelProcessor): + """Implementation of the ModelProcessor in the Revit context. + """ def __init__( self, material_processor: MaterialProcessor, @@ -32,7 +36,14 @@ class RevitModelProcessor(ModelProcessor): def process_elements(self, model: Any) -> None: """Process all elements in the model hierarchically. - Model → Levels → Type Groups → Elements""" + Model → Levels → Type Groups → Elements. + + Args: + model (Any): commit root + + Raises: + ValueError: root["elements"] must exist + """ levels = getattr(model, ELEMENTS, None) if not levels: raise ValueError("Invalid model: missing elements at root.") @@ -41,7 +52,14 @@ class RevitModelProcessor(ModelProcessor): self._process_level(level) def _process_level(self, level: Any) -> None: - """Process a single level in the model""" + """Process all groups contained on a level. + + Args: + level (Any): root["elements"][i] → level collection + + Raises: + ValueError: root["elements"][i]["elements"] must exist + """ type_groups = getattr(level, ELEMENTS, None) if not type_groups: level_name = getattr(level, NAME, "Unknown") @@ -54,7 +72,16 @@ class RevitModelProcessor(ModelProcessor): self._process_type_group(type_group, level_name) def _process_type_group(self, type_group: Any, level_name: str) -> None: - """Process a group of elements of the same type""" + """Process a group of elements of the same type + + Args: + type_group (Any): group to process + level_name (str): associated level of the groups + + Raises: + ValueError: root["elements"][i]["elements"][i]["elements"] must exist + ValueError: root["elements"][i]["elements"][i]["elements"][i]["elements"] must exist + """ groups = getattr(type_group, ELEMENTS, None) if not groups: type_name = getattr(type_group, NAME, "Unknown") @@ -72,23 +99,30 @@ class RevitModelProcessor(ModelProcessor): for revit_object in revit_objects: self.process_element(level_name, type_name, revit_object) - def process_element(self, level: str, type_name: str, revit_object: Any) -> None: - """Process a single element following original logic exactly""" + def process_element(self, level: str, type_name: str, model_object: Any) -> None: + """Processing of actual model object after successful traversal of the commit. + + Args: + level (str): associated level of the object + type_name (str): family / type of the object + revit_object (Any): speckle object + """ # We can probably straight up skip Line and Arc. Logging it as a warning is dumb - speckle_type = getattr(revit_object, SPECKLE_TYPE, None) + # TODO: Possibly add to logger for info? Not a warning though. + speckle_type = getattr(model_object, SPECKLE_TYPE, None) if speckle_type in [LINE, ARC, CIRCLE]: - return # TODO: Possibly add to logger for info? Not a warning though. + return - element_id = getattr(revit_object, ID, None) + element_id = getattr(model_object, ID, None) if not element_id: return # Check Material Quantities - properties = getattr(revit_object, PROPERTIES, None) + properties = getattr(model_object, PROPERTIES, None) if not properties: self._logger.log_warning( - f"Missing Material Quantities", + "Missing Material Quantities", object_id=element_id, missing_property=PROPERTIES, ) @@ -96,7 +130,7 @@ class RevitModelProcessor(ModelProcessor): material_quantities = properties.get(MATERIAL_QUANTITIES, None) if not material_quantities: self._logger.log_warning( - f"Missing Material Quantities", + "Missing Material Quantities", object_id=element_id, missing_property=MATERIAL_QUANTITIES, ) diff --git a/src/utils/constants.py b/src/utils/constants.py index 40aa30a..ab3df33 100644 --- a/src/utils/constants.py +++ b/src/utils/constants.py @@ -1,3 +1,5 @@ +# TODO: Implementation specific constants. + REQUIRED_PROPERTIES = ["volume", "density", "structuralAsset"] # Keys diff --git a/src/validators/validators.py b/src/validators/validators.py deleted file mode 100644 index e69de29..0000000