From b2ac005be6c7f7ffc8311ff41854fb2451681264 Mon Sep 17 00:00:00 2001 From: Dinis Cruz Date: Mon, 20 Jan 2025 11:41:03 +0000 Subject: [PATCH] improved coding formating guidelines further Type_Safe refactoring into shared classes Type_Safe__Validation, Type_Safe__Shared__Variables --- docs/dev/Python-code-formatting-guidelines.md | 130 +++++++++++++++--- .../{steps => shared}/Type_Safe__Cache.py | 0 .../Type_Safe__Raise_Exception.py | 5 +- .../shared/Type_Safe__Shared__Variables.py | 5 + .../type_safe/shared/Type_Safe__Validation.py | 39 ++++++ osbot_utils/type_safe/shared/__init__.py | 0 .../steps/Type_Safe__Step__Class_Kwargs.py | 124 ++++++++--------- .../type_safe/steps/Type_Safe__Validation.py | 10 -- tests/unit/base_classes/test_Cache_Pickle.py | 6 +- .../test_Performance_Measure__Session.py | 14 +- 10 files changed, 226 insertions(+), 107 deletions(-) rename osbot_utils/type_safe/{steps => shared}/Type_Safe__Cache.py (100%) rename osbot_utils/type_safe/{steps => shared}/Type_Safe__Raise_Exception.py (82%) create mode 100644 osbot_utils/type_safe/shared/Type_Safe__Shared__Variables.py create mode 100644 osbot_utils/type_safe/shared/Type_Safe__Validation.py create mode 100644 osbot_utils/type_safe/shared/__init__.py delete mode 100644 osbot_utils/type_safe/steps/Type_Safe__Validation.py diff --git a/docs/dev/Python-code-formatting-guidelines.md b/docs/dev/Python-code-formatting-guidelines.md index e2144a5d..1724d44e 100644 --- a/docs/dev/Python-code-formatting-guidelines.md +++ b/docs/dev/Python-code-formatting-guidelines.md @@ -35,15 +35,15 @@ PEP-8's formatting guidelines, while well-intentioned, can create several practi ```python # PEP-8 style self.method_call( - parameter_one="value", +parameter_one="value", parameter_two="value", parameter_three="value" ) # This style self.method_call(parameter_one = "value", - parameter_two = "value", - parameter_three = "value") + parameter_two = "value", + parameter_three = "value") ``` 2. Loss of Visual Patterns @@ -75,7 +75,8 @@ class SomeClass: # This style - related elements together class SomeClass: def __init__(self, param_one, - param_two ): + param_two )\ + -> None: self.param_one = param_one self.param_two = param_two ``` @@ -126,14 +127,109 @@ from osbot_utils.helpers.Random_Guid import Random_Guid from osbot_utils.helpers.Safe_Id import Safe_Id ``` -## Method Documentation -Method documentation should be provided as inline comments on the same line as the method definition at the same column (starting on 80): +## Method Signature Formatting + +### Core Principles + +1. **Visual Lanes** + - Parameters stack vertically + - Type hints align in their own column + - Comments align at a consistent position + - Return types appear on a new line after continuation + +2. **Information Density** + - Each line contains one parameter + - Type information is immediately visible + - Purpose is clear from aligned comment + - Related elements stay visually grouped + +### Method Signature Layout + +```python +def method_name(self, first_param : Type1 , # Method purpose comment + second_param : Type2 , # Aligned at column 80 + third_param : Type3 = None )\ # Default values align with type + -> ReturnType: # Return on new line +``` + +Key aspects: +- Method name starts at indent level +- Parameters indent to align with opening parenthesis +- Type hints align in their own column +- Commas align in their own column +- Backslash continuation before return type +- Return type aligns with self variable name +- Comments align at column 80 +- vertical alignment on : , # + +### Parameter Documentation + +```python +def complex_operation(self, data_input : Dict[str, Any] , # Primary data structure + config_options : Optional[Config] , # Processing configuration + max_retries : int = 3 , # Maximum retry attempts + timeout_ms : float = 1000.0 )\ # Operation timeout + -> Tuple[Results, Metrics]: # Returns results and metrics +``` + +Guidelines: +- Parameter names should be descriptive +- Type hints should be as specific as possible +- Default values align with type hints +- Comments describe parameter purpose +- Return type comment describes what is returned + +### Method Groups and Spacing + +Methods should be grouped by functionality with clear separation: + +```python + # Core initialization methods + def __init__(self, config: Config )\ # Initialize with configuration + -> None: + + def setup(self, options: Dict[str, Any] )\ # Configure processing options + -> bool: + + + # Data validation methods + def validate_input(self, data : InputData , # Validate input format + strict_mode : bool = False )\ # Enable strict validation + -> ValidationResult: + + def validate_output(self, result : OutputData , # Validate output format + thresholds : Thresholds )\ # Validation thresholds + -> bool: + + + # Processing methods + def process_item(self, item : DataItem , # Process single data item + settings : Settings )\ # Processing settings + -> ProcessedItem: +``` +Note how the return type name assigns with the variable self, and there is always at least one space before the : and the , + +### Complex Type Signatures + +For methods with complex type signatures: ```python -def setUp(self): # Initialize test data -def test_init(self): # Tests basic initialization and type checking +def process_batch(self, items : List[DataItem] , # Batch of items to process + batch_config : BatchConfig , # Batch processing config + error_handler : ErrorHandler , # Handles processing errors + retry_strategy : Optional[Strategy] , # Retry strategy to use + metrics_callback : Callable[[Metrics], None] = None )\ # Metrics reporting callback + -> BatchResults: # Processed batch results ``` +Guidelines: +- Break complex generic types at logical points +- Align nested type parameters +- Keep related type information together +- Document complex types in comments + + + ## Variable Assignment Alignment Variable assignments should be aligned on the `=` operator: @@ -162,10 +258,10 @@ Note that: Assert statements should be aligned on the comparison operator: ```python -assert type(self.node) is Schema__MGraph__Node -assert self.node.node_data == self.node_data -assert self.node.value == "test_node_value" -assert len(self.node.attributes) == 1 +assert type(self.node) is Schema__MGraph__Node +assert self.node.node_data == self.node_data +assert self.node.value == "test_node_value" +assert len(self.node.attributes) == 1 assert self.node.attributes[self.attribute.attribute_id] == self.attribute ``` @@ -173,11 +269,11 @@ assert self.node.attributes[self.attribute.attribute_id] == self.attribute Dictionary literals in constructor calls should maintain alignment while using minimal line breaks: ```python -node = Schema__MGraph__Node(attributes={attr_1.attribute_id: attr_1, - attr_2.attribute_id: attr_2}, - node_config=self.node_data, - node_type=Schema__MGraph__Node, - value="test_node_value") +node = Schema__MGraph__Node(attributes = {attr_1.attribute_id: attr_1 , + attr_2.attribute_id: attr_2} , + node_config = self.node_data , + node_type = Schema__MGraph__Node , + value = "test_node_value" ) ``` ## Test Class Structure diff --git a/osbot_utils/type_safe/steps/Type_Safe__Cache.py b/osbot_utils/type_safe/shared/Type_Safe__Cache.py similarity index 100% rename from osbot_utils/type_safe/steps/Type_Safe__Cache.py rename to osbot_utils/type_safe/shared/Type_Safe__Cache.py diff --git a/osbot_utils/type_safe/steps/Type_Safe__Raise_Exception.py b/osbot_utils/type_safe/shared/Type_Safe__Raise_Exception.py similarity index 82% rename from osbot_utils/type_safe/steps/Type_Safe__Raise_Exception.py rename to osbot_utils/type_safe/shared/Type_Safe__Raise_Exception.py index 5b856b9d..aa181f69 100644 --- a/osbot_utils/type_safe/steps/Type_Safe__Raise_Exception.py +++ b/osbot_utils/type_safe/shared/Type_Safe__Raise_Exception.py @@ -1,6 +1,5 @@ -from typing import Any - -from osbot_utils.type_safe.steps.Type_Safe__Validation import IMMUTABLE_TYPES +from typing import Any +from osbot_utils.type_safe.shared.Type_Safe__Shared__Variables import IMMUTABLE_TYPES class Type_Safe__Raise_Exception: diff --git a/osbot_utils/type_safe/shared/Type_Safe__Shared__Variables.py b/osbot_utils/type_safe/shared/Type_Safe__Shared__Variables.py new file mode 100644 index 00000000..8c2fb269 --- /dev/null +++ b/osbot_utils/type_safe/shared/Type_Safe__Shared__Variables.py @@ -0,0 +1,5 @@ +import types +from enum import EnumMeta + + +IMMUTABLE_TYPES = (bool, int, float, complex, str, tuple, frozenset, bytes, types.NoneType, EnumMeta, type) \ No newline at end of file diff --git a/osbot_utils/type_safe/shared/Type_Safe__Validation.py b/osbot_utils/type_safe/shared/Type_Safe__Validation.py new file mode 100644 index 00000000..4edacc4f --- /dev/null +++ b/osbot_utils/type_safe/shared/Type_Safe__Validation.py @@ -0,0 +1,39 @@ +import types +from enum import EnumMeta +from typing import Any +from osbot_utils.type_safe.shared.Type_Safe__Shared__Variables import IMMUTABLE_TYPES +from osbot_utils.utils.Objects import obj_is_type_union_compatible +from osbot_utils.type_safe.shared.Type_Safe__Raise_Exception import type_safe_raise_exception + + +class Type_Safe__Validation: + + # todo: add cache support to this method + def should_skip_type_check(self, var_type): # Determine if type checking should be skipped + from typing import get_origin, Annotated + return (get_origin(var_type) is Annotated or + get_origin(var_type) is type) + + def should_skip_var(self, var_name: str, var_value: Any) -> bool: # Determines if variable should be skipped during MRO processing + if var_name.startswith('__'): # skip internal variables + return True + if isinstance(var_value, types.FunctionType): # skip instance functions + return True + if isinstance(var_value, classmethod): # skip class methods + return True + if isinstance(var_value, property): # skip property descriptors + return True + return False + + def validate_type_immutability(self, var_name: str, var_type: Any) -> None: # Validates that type is immutable or in supported format + if var_type not in IMMUTABLE_TYPES and var_name.startswith('__') is False: # if var_type is not one of the IMMUTABLE_TYPES or is an __ internal + if obj_is_type_union_compatible(var_type, IMMUTABLE_TYPES) is False: # if var_type is not something like Optional[Union[int, str]] + if var_type not in IMMUTABLE_TYPES or type(var_type) not in IMMUTABLE_TYPES: + if not isinstance(var_type, EnumMeta): + type_safe_raise_exception.immutable_type_error(var_name, var_type) + + def validate_variable_type(self, var_name, var_type, var_value): # Validate type compatibility + if var_type and not isinstance(var_value, var_type): + type_safe_raise_exception.type_mismatch_error(var_name, var_type, var_value) + +type_safe_validation = Type_Safe__Validation() diff --git a/osbot_utils/type_safe/shared/__init__.py b/osbot_utils/type_safe/shared/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/osbot_utils/type_safe/steps/Type_Safe__Step__Class_Kwargs.py b/osbot_utils/type_safe/steps/Type_Safe__Step__Class_Kwargs.py index cc341405..571b1537 100644 --- a/osbot_utils/type_safe/steps/Type_Safe__Step__Class_Kwargs.py +++ b/osbot_utils/type_safe/steps/Type_Safe__Step__Class_Kwargs.py @@ -1,100 +1,90 @@ -import types -from enum import EnumMeta -from typing import Dict, Any, Type, get_origin, Annotated, get_args -from osbot_utils.type_safe.steps.Type_Safe__Cache import Type_Safe__Cache, type_safe_cache -from osbot_utils.type_safe.steps.Type_Safe__Raise_Exception import type_safe_raise_exception -from osbot_utils.type_safe.steps.Type_Safe__Step__Default_Value import type_safe_step_default_value -from osbot_utils.type_safe.steps.Type_Safe__Validation import IMMUTABLE_TYPES -from osbot_utils.utils.Objects import obj_is_type_union_compatible +from typing import Dict, Any, Type +from osbot_utils.type_safe.shared.Type_Safe__Cache import Type_Safe__Cache, type_safe_cache +from osbot_utils.type_safe.shared.Type_Safe__Validation import type_safe_validation +from osbot_utils.type_safe.steps.Type_Safe__Step__Default_Value import type_safe_step_default_value -class Type_Safe__Step__Class_Kwargs: # Cache for class-level keyword arguments and related information.""" - type_safe_cache : Type_Safe__Cache + +class Type_Safe__Step__Class_Kwargs: # Handles class-level keyword arguments processing + + type_safe_cache : Type_Safe__Cache # Cache component reference def __init__(self): - self.type_safe_cache = type_safe_cache + self.type_safe_cache = type_safe_cache # Initialize with singleton cache - def get_cls_kwargs(self, cls: Type, include_base_classes: bool = True) -> Dict[str, Any]: - if not hasattr(cls, '__mro__'): + def get_cls_kwargs(self, cls : Type , # Get class keyword arguments + include_base_classes : bool = True)\ + -> Dict[str, Any]: + if not hasattr(cls, '__mro__'): # Handle non-class inputs return {} - base_classes = type_safe_cache.get_class_mro(cls) - if not include_base_classes: + base_classes = type_safe_cache.get_class_mro(cls) # Get class hierarchy + if not include_base_classes: # Limit to current class if needed base_classes = base_classes[:1] - kwargs = {} + kwargs = {} # Process inheritance chain for base_cls in base_classes: - self.process_mro_class (base_cls, kwargs) - self.process_annotations(cls, base_cls, kwargs) + self.process_mro_class (base_cls, kwargs) # Handle MRO class + self.process_annotations(cls, base_cls, kwargs) # Process annotations return kwargs - def handle_undefined_var(self, cls, kwargs, var_name, var_type): # Handle variables not yet defined in base class - if var_name in kwargs: + def handle_undefined_var(self, cls : Type , # Handle undefined class variables + kwargs : Dict[str, Any] , + var_name : str , + var_type : Type )\ + -> None: + if var_name in kwargs: # Skip if already defined return - var_value = type_safe_step_default_value.default_value(cls, var_type) - kwargs[var_name] = var_value - - def handle_defined_var(self, base_cls, var_name, var_type): # Handle variables already defined in base class - var_value = getattr(base_cls, var_name) - if var_value is None: # Allow None assignments on constructor + var_value = type_safe_step_default_value.default_value(cls, var_type) # Get default value + kwargs[var_name] = var_value # Store in kwargs + + def handle_defined_var(self, base_cls : Type , # Handle defined class variables + var_name : str , + var_type : Type )\ + -> None: + var_value = getattr(base_cls, var_name) # Get current value + if var_value is None: # Allow None assignments return - if self.should_skip_type_check(var_type): + if type_safe_validation.should_skip_type_check(var_type): # Skip validation if needed return - self.validate_variable_type (var_name, var_type, var_value) - self.validate_type_immutability(var_name, var_type) + type_safe_validation.validate_variable_type (var_name, var_type, var_value) # Validate type + type_safe_validation.validate_type_immutability(var_name, var_type) # Validate immutability - def process_annotation(self, cls, base_cls, kwargs, var_name, var_type): # Process type annotations for class variables - if not hasattr(base_cls, var_name): + def process_annotation(self, cls : Type , # Process single annotation + base_cls : Type , + kwargs : Dict[str, Any] , + var_name : str , + var_type : Type )\ + -> None: + if not hasattr(base_cls, var_name): # Handle undefined variables self.handle_undefined_var(cls, kwargs, var_name, var_type) - else: + else: # Handle defined variables self.handle_defined_var(base_cls, var_name, var_type) - def process_annotations(self, cls, base_cls, kwargs): - if hasattr(base_cls,'__annotations__'): # can only do type safety checks if the class does not have annotations + def process_annotations(self, cls : Type , # Process all annotations + base_cls : Type , + kwargs : Dict[str, Any] )\ + -> None: + if hasattr(base_cls, '__annotations__'): # Process if annotations exist for var_name, var_type in type_safe_cache.get_class_annotations(base_cls): self.process_annotation(cls, base_cls, kwargs, var_name, var_type) - def process_mro_class(self, base_cls, kwargs): - if base_cls is object: # Skip the base 'object' class + def process_mro_class(self, base_cls : Type , # Process class in MRO chain + kwargs : Dict[str, Any] )\ + -> None: + if base_cls is object: # Skip object class return - class_variables = type_safe_cache.get_valid_class_variables(base_cls, self.should_skip_var) + class_variables = type_safe_cache.get_valid_class_variables( # Get valid class variables + base_cls, + type_safe_validation.should_skip_var) - for name, value in class_variables.items(): + for name, value in class_variables.items(): # Add non-existing variables if name not in kwargs: kwargs[name] = value - def should_skip_var(self, var_name: str, var_value: Any) -> bool: # Determines if variable should be skipped during MRO processing - if var_name.startswith('__'): # skip internal variables - return True - if isinstance(var_value, types.FunctionType): # skip instance functions - return True - if isinstance(var_value, classmethod): # skip class methods - return True - if isinstance(var_value, property): # skip property descriptors - return True - return False - - def should_skip_type_check(self, var_type): # Determine if type checking should be skipped - return (get_origin(var_type) is Annotated or - get_origin(var_type) is type) - - - - - def validate_type_immutability(self, var_name: str, var_type: Any) -> None: # Validates that type is immutable or in supported format - if var_type not in IMMUTABLE_TYPES and var_name.startswith('__') is False: # if var_type is not one of the IMMUTABLE_TYPES or is an __ internal - if obj_is_type_union_compatible(var_type, IMMUTABLE_TYPES) is False: # if var_type is not something like Optional[Union[int, str]] - if var_type not in IMMUTABLE_TYPES or type(var_type) not in IMMUTABLE_TYPES: - if not isinstance(var_type, EnumMeta): - type_safe_raise_exception.immutable_type_error(var_name, var_type) - - def validate_variable_type(self, var_name, var_type, var_value): # Validate type compatibility - if var_type and not isinstance(var_value, var_type): - type_safe_raise_exception.type_mismatch_error(var_name, var_type, var_value) - # Create singleton instance type_safe_step_class_kwargs = Type_Safe__Step__Class_Kwargs() \ No newline at end of file diff --git a/osbot_utils/type_safe/steps/Type_Safe__Validation.py b/osbot_utils/type_safe/steps/Type_Safe__Validation.py deleted file mode 100644 index da4d7914..00000000 --- a/osbot_utils/type_safe/steps/Type_Safe__Validation.py +++ /dev/null @@ -1,10 +0,0 @@ -import types -from enum import EnumMeta - -IMMUTABLE_TYPES = (bool, int, float, complex, str, tuple, frozenset, bytes, types.NoneType, EnumMeta, type) - -class Type_Safe__Validation: - pass - - -type_safe_validation = Type_Safe__Validation() diff --git a/tests/unit/base_classes/test_Cache_Pickle.py b/tests/unit/base_classes/test_Cache_Pickle.py index bb73d774..0ae5c67b 100644 --- a/tests/unit/base_classes/test_Cache_Pickle.py +++ b/tests/unit/base_classes/test_Cache_Pickle.py @@ -1,7 +1,7 @@ -from unittest import TestCase -from osbot_utils.base_classes.Cache_Pickle import Cache_Pickle +from unittest import TestCase +from osbot_utils.base_classes.Cache_Pickle import Cache_Pickle from osbot_utils.decorators.methods.context import context -from osbot_utils.utils.Files import folder_exists, current_temp_folder, pickle_load_from_file +from osbot_utils.utils.Files import folder_exists, current_temp_folder, pickle_load_from_file diff --git a/tests/unit/testing/performance/test_Performance_Measure__Session.py b/tests/unit/testing/performance/test_Performance_Measure__Session.py index 80dd9964..ddc9a568 100644 --- a/tests/unit/testing/performance/test_Performance_Measure__Session.py +++ b/tests/unit/testing/performance/test_Performance_Measure__Session.py @@ -44,14 +44,14 @@ class An_Class_6(Type_Safe): an_str: str = '42' - Performance_Measure__Session().measure(str ).print().assert_time(self.time_100_ns, self.time_0_ns ) - Performance_Measure__Session().measure(Random_Guid).print().assert_time(self.time_3_kns , self.time_5_kns, self.time_6_kns ) - Performance_Measure__Session().measure(An_Class_1 ).print().assert_time(self.time_100_ns ) + Performance_Measure__Session().measure(str ).print().assert_time(self.time_100_ns, self.time_0_ns ) + Performance_Measure__Session().measure(Random_Guid).print().assert_time(self.time_3_kns , self.time_5_kns, self.time_6_kns , self.time_7_kns ) + Performance_Measure__Session().measure(An_Class_1 ).print().assert_time(self.time_100_ns ) Performance_Measure__Session().measure(An_Class_2 ).print().assert_time(self.time_3_kns , self.time_4_kns , self.time_5_kns , self.time_6_kns, self.time_7_kns ) - Performance_Measure__Session().measure(An_Class_3 ).print().assert_time(self.time_8_kns , self.time_9_kns ,self.time_10_kns, self.time_20_kns ) - Performance_Measure__Session().measure(An_Class_4 ).print().assert_time(self.time_8_kns , self.time_9_kns ,self.time_10_kns, self.time_20_kns ) - Performance_Measure__Session().measure(An_Class_5 ).print().assert_time(self.time_8_kns , self.time_9_kns ,self.time_10_kns, self.time_20_kns ) - Performance_Measure__Session().measure(An_Class_6 ).print().assert_time(self.time_7_kns , self.time_8_kns , self.time_10_kns, self.time_20_kns ) + Performance_Measure__Session().measure(An_Class_3 ).print().assert_time(self.time_8_kns , self.time_9_kns ,self.time_10_kns, self.time_20_kns ) + Performance_Measure__Session().measure(An_Class_4 ).print().assert_time(self.time_8_kns , self.time_9_kns ,self.time_10_kns, self.time_20_kns ) + Performance_Measure__Session().measure(An_Class_5 ).print().assert_time(self.time_8_kns , self.time_9_kns ,self.time_10_kns, self.time_20_kns ) + Performance_Measure__Session().measure(An_Class_6 ).print().assert_time(self.time_7_kns , self.time_8_kns , self.time_10_kns, self.time_20_kns ) # def test_dissaembly_both_paths(self): # from osbot_utils.type_safe.Cache__Class_Kwargs import Cache__Class_Kwargs