pydasa 0.4.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. pydasa/__init__.py +103 -0
  2. pydasa/_version.py +6 -0
  3. pydasa/analysis/__init__.py +0 -0
  4. pydasa/analysis/scenario.py +584 -0
  5. pydasa/analysis/simulation.py +1158 -0
  6. pydasa/context/__init__.py +0 -0
  7. pydasa/context/conversion.py +11 -0
  8. pydasa/context/system.py +17 -0
  9. pydasa/context/units.py +15 -0
  10. pydasa/core/__init__.py +15 -0
  11. pydasa/core/basic.py +287 -0
  12. pydasa/core/cfg/default.json +136 -0
  13. pydasa/core/constants.py +27 -0
  14. pydasa/core/io.py +102 -0
  15. pydasa/core/setup.py +269 -0
  16. pydasa/dimensional/__init__.py +0 -0
  17. pydasa/dimensional/buckingham.py +728 -0
  18. pydasa/dimensional/fundamental.py +146 -0
  19. pydasa/dimensional/model.py +1077 -0
  20. pydasa/dimensional/vaschy.py +633 -0
  21. pydasa/elements/__init__.py +19 -0
  22. pydasa/elements/parameter.py +218 -0
  23. pydasa/elements/specs/__init__.py +22 -0
  24. pydasa/elements/specs/conceptual.py +161 -0
  25. pydasa/elements/specs/numerical.py +469 -0
  26. pydasa/elements/specs/statistical.py +229 -0
  27. pydasa/elements/specs/symbolic.py +394 -0
  28. pydasa/serialization/__init__.py +27 -0
  29. pydasa/serialization/parser.py +133 -0
  30. pydasa/structs/__init__.py +0 -0
  31. pydasa/structs/lists/__init__.py +0 -0
  32. pydasa/structs/lists/arlt.py +578 -0
  33. pydasa/structs/lists/dllt.py +18 -0
  34. pydasa/structs/lists/ndlt.py +262 -0
  35. pydasa/structs/lists/sllt.py +746 -0
  36. pydasa/structs/tables/__init__.py +0 -0
  37. pydasa/structs/tables/htme.py +182 -0
  38. pydasa/structs/tables/scht.py +774 -0
  39. pydasa/structs/tools/__init__.py +0 -0
  40. pydasa/structs/tools/hashing.py +53 -0
  41. pydasa/structs/tools/math.py +149 -0
  42. pydasa/structs/tools/memory.py +54 -0
  43. pydasa/structs/types/__init__.py +0 -0
  44. pydasa/structs/types/functions.py +131 -0
  45. pydasa/structs/types/generics.py +54 -0
  46. pydasa/validations/__init__.py +0 -0
  47. pydasa/validations/decorators.py +510 -0
  48. pydasa/validations/error.py +100 -0
  49. pydasa/validations/patterns.py +32 -0
  50. pydasa/workflows/__init__.py +1 -0
  51. pydasa/workflows/influence.py +497 -0
  52. pydasa/workflows/phenomena.py +529 -0
  53. pydasa/workflows/practical.py +765 -0
  54. pydasa-0.4.7.dist-info/METADATA +320 -0
  55. pydasa-0.4.7.dist-info/RECORD +58 -0
  56. pydasa-0.4.7.dist-info/WHEEL +5 -0
  57. pydasa-0.4.7.dist-info/licenses/LICENSE +674 -0
  58. pydasa-0.4.7.dist-info/top_level.txt +1 -0
@@ -0,0 +1,510 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Module decorators.py
4
+ ===========================================
5
+
6
+ Decorator-based validation system for PyDASA attributes.
7
+
8
+ This module provides reusable decorators for property setters, eliminating
9
+ the need for separate validation methods and reducing boilerplate code.
10
+
11
+ Functions:
12
+ **validate_type**: Validates value against expected type(s)
13
+ **validate_emptiness**: Ensures string values are non-empty
14
+ **validate_choices**: Validates value is in allowed set of choices
15
+ **validate_range**: Validates numeric value is within specified range
16
+ **validate_index**: Validates integer values with negativity control
17
+ **validate_pattern**: Validates string matches regex pattern(s) or is alphanumeric
18
+ **validate_custom**: Custom validation logic
19
+ """
20
+
21
+ # native python modules
22
+ from functools import wraps
23
+ from enum import Enum
24
+ from typing import Callable, Any, Union, Type, Optional # , Tuple
25
+ import re
26
+
27
+
28
+ def validate_type(*expected_types: type, allow_none: bool = True) -> Callable:
29
+ """*validate_type()* Decorator to validate argument type against expected type(s).
30
+
31
+ Args:
32
+ *expected_types (type): One or more expected types for validation.
33
+ allow_none (bool, optional): Whether None values are allowed. Defaults to True.
34
+
35
+ Raises:
36
+ ValueError: If value is None when allow_none is False.
37
+ ValueError: If value type does not match any of the expected types.
38
+
39
+ Returns:
40
+ Callable: Decorated function with type validation.
41
+
42
+ Example:
43
+ @property
44
+ def unit(self) -> str:
45
+ return self._unit
46
+
47
+ @unit.setter
48
+ @validate_type(str)
49
+ def unit(self, val: str) -> None:
50
+ self._unit = val
51
+
52
+ # Multiple types
53
+ @value.setter
54
+ @validate_type(int, float)
55
+ def value(self, val: Union[int, float]) -> None:
56
+ self._value = val
57
+ """
58
+ def decorator(func: Callable) -> Callable:
59
+ @wraps(func)
60
+ def wrapper(self, value: Any) -> Any:
61
+ # if value is None
62
+ if value is None:
63
+ if not allow_none:
64
+ _msg = f"{func.__name__} cannot be None."
65
+ raise ValueError(_msg)
66
+ return func(self, value)
67
+
68
+ # if value type is incorrect
69
+ if not isinstance(value, expected_types):
70
+ type_names = " or ".join(t.__name__ for t in expected_types)
71
+ _msg = f"{func.__name__} must be {type_names}, "
72
+ _msg += f"got {type(value).__name__}."
73
+ raise ValueError(_msg)
74
+ # otherwise, return the original function
75
+ return func(self, value)
76
+ return wrapper # return the wrapper
77
+ return decorator # return the decorator
78
+
79
+
80
+ def validate_emptiness(strip: bool = True) -> Callable:
81
+ """*validate_emptiness()* Decorator to ensure values are non-empty.
82
+
83
+ Handles strings, dictionaries, lists, tuples, and other collections.
84
+ For strings, optionally strips whitespace before checking.
85
+
86
+ Args:
87
+ strip (bool, optional): Whether to strip whitespace before checking strings. Defaults to True.
88
+
89
+ Raises:
90
+ ValueError: If string is empty/whitespace-only, or if collection has no elements.
91
+
92
+ Returns:
93
+ Callable: Decorated function with non-empty validation.
94
+
95
+ Example:
96
+ @unit.setter
97
+ @validate_type(str)
98
+ @validate_emptiness()
99
+ def unit(self, val: str) -> None:
100
+ self._unit = val
101
+
102
+ @variables.setter
103
+ @validate_type(dict)
104
+ @validate_emptiness()
105
+ def variables(self, val: dict) -> None:
106
+ self._variables = val
107
+ """
108
+ def decorator(func: Callable) -> Callable:
109
+ @wraps(func)
110
+ def wrapper(self, value: Any) -> Any:
111
+ # if value is not None, check for emptiness
112
+ if value is not None:
113
+ # Handle strings separately to allow strip functionality
114
+ if isinstance(value, str):
115
+ check_val = value.strip() if strip else value
116
+ if not check_val:
117
+ _msg = f"{func.__name__} must be a non-empty string. "
118
+ _msg += f"Provided: {repr(value)}."
119
+ raise ValueError(_msg)
120
+ # Handle collections (dict, list, tuple, set, etc.)
121
+ elif hasattr(value, '__len__'):
122
+ if len(value) == 0:
123
+ type_name = type(value).__name__
124
+ _msg = f"{func.__name__} must be a non-empty {type_name}. "
125
+ _msg += f"Provided: {repr(value)}."
126
+ raise ValueError(_msg)
127
+ # otherwise, call the original function
128
+ return func(self, value)
129
+ return wrapper # return the wrapper
130
+ return decorator # return the decorator
131
+
132
+
133
+ def validate_choices(choices: Union[dict, set, list, tuple, Type[Enum]],
134
+ allow_none: bool = False,
135
+ case_sensitive: bool = False) -> Callable:
136
+ """*validate_choices()* Decorator to validate value is in allowed set of choices.
137
+
138
+ Args:
139
+ choices (Union[dict, set, list, tuple, Type[Enum]]): Dictionary, set, list, tuple, or Enum type of allowed values.
140
+ allow_none (bool, optional): Whether None values are allowed. Defaults to False.
141
+ case_sensitive (bool, optional): Whether string comparison is case-sensitive. Defaults to False.
142
+
143
+ Raises:
144
+ ValueError: If value is not in the allowed choices.
145
+
146
+ Returns:
147
+ Callable: Decorated function with choice validation.
148
+
149
+ Example:
150
+ from pydasa.core.setup import Frameworks
151
+
152
+ @fwk.setter
153
+ @validate_type(str)
154
+ @validate_choices(Frameworks.values())
155
+ def fwk(self, val: str) -> None:
156
+ self._fwk = val.upper()
157
+
158
+ # Case-sensitive choices
159
+ @status.setter
160
+ @validate_choices(["Active", "Inactive"], case_sensitive=True)
161
+ def status(self, val: str) -> None:
162
+ self._status = val
163
+ """
164
+ # Convert choices to set for O(1) lookup - extract enum names when needed
165
+ if isinstance(choices, dict):
166
+ valid_choices = set(choices.keys())
167
+ elif isinstance(choices, type) and issubclass(choices, Enum):
168
+ # Enum class passed directly (e.g., Frameworks)
169
+ valid_choices = {member.name for member in choices}
170
+ else:
171
+ # Handle collections: check if they contain Enum members
172
+ first_elem = next(iter(choices), None) if choices else None
173
+ if first_elem and isinstance(first_elem, Enum):
174
+ # Collection of Enum members (e.g., tuple of Frameworks members)
175
+ valid_choices = {member.name for member in choices}
176
+ else:
177
+ # Plain collection of strings or other values
178
+ valid_choices = set(choices)
179
+
180
+ def decorator(func: Callable) -> Callable:
181
+ @wraps(func)
182
+ def wrapper(self, value: Any) -> Any:
183
+ # if value is None
184
+ if value is None:
185
+ if not allow_none:
186
+ _msg = f"{func.__name__} cannot be None."
187
+ raise ValueError(_msg)
188
+ return func(self, value)
189
+
190
+ # Extract the actual value to check
191
+ # If value is an Enum member, use its name
192
+ if isinstance(value, Enum):
193
+ actual_value = value.name
194
+ else:
195
+ actual_value = value
196
+
197
+ # if case-insensitive, adjust value and choices
198
+ if case_sensitive:
199
+ check_val = actual_value
200
+ compare_set = valid_choices
201
+ # otherwise, use upper-case for comparison
202
+ else:
203
+ check_val = str(actual_value).upper()
204
+ compare_set = {str(c).upper() for c in valid_choices}
205
+
206
+ # if value not in choices, raise error
207
+ if check_val not in compare_set:
208
+ _msg = f"Invalid {func.__name__}: {value}. "
209
+ # Format choices nicely - if Enum members, use their name/value only
210
+ choice_strs = [c.name if isinstance(c, Enum) else str(c) for c in valid_choices]
211
+ _msg += f"Must be one of: {', '.join(choice_strs)}."
212
+ raise ValueError(_msg)
213
+
214
+ # otherwise, call the original function
215
+ return func(self, value)
216
+ return wrapper # return the wrapper
217
+ return decorator # return the decorator
218
+
219
+
220
+ def validate_index(allow_zero: bool = True,
221
+ allow_negative: bool = False) -> Callable:
222
+ """*validate_index()* Decorator to validate integer values with negativity and zero control.
223
+
224
+ Args:
225
+ allow_zero (bool, optional): Whether zero is allowed. Defaults to True.
226
+ allow_negative (bool, optional): Whether negative integers are allowed. Defaults to False.
227
+
228
+ Raises:
229
+ ValueError: If value is not an integer.
230
+ ValueError: If negative value when allow_negative is False.
231
+ ValueError: If zero value when allow_zero is False.
232
+
233
+ Returns:
234
+ Callable: Decorated function with integer validation.
235
+
236
+ Example:
237
+ # Non-negative integers only
238
+ @idx.setter
239
+ @validate_index(allow_negative=False)
240
+ def idx(self, val: int) -> None:
241
+ self._idx = val
242
+
243
+ # Positive integers only (no zero)
244
+ @count.setter
245
+ @validate_index(allow_negative=False, allow_zero=False)
246
+ def count(self, val: int) -> None:
247
+ self._count = val
248
+ """
249
+ def decorator(func: Callable) -> Callable:
250
+ @wraps(func)
251
+ def wrapper(self, value: Any) -> Any:
252
+ # if value is not None, perform checks
253
+ if value is not None:
254
+ # if not an integer, raise error
255
+ if not isinstance(value, int):
256
+ _msg = f"{func.__name__} must be an integer. "
257
+ _msg += f"Provided: {value} ({type(value).__name__})."
258
+ raise ValueError(_msg)
259
+
260
+ # if negative not allowed and value es velow zero
261
+ if not allow_negative and value < 0:
262
+ _msg = f"{func.__name__} must be non-negative. "
263
+ _msg += f"Provided: {value}."
264
+ raise ValueError(_msg)
265
+
266
+ # if zero not allowed and value is zero
267
+ if not allow_zero and value == 0:
268
+ _msg = f"{func.__name__} cannot be zero. "
269
+ _msg += f"Provided: {value}."
270
+ raise ValueError(_msg)
271
+
272
+ # otherwise, call the original function
273
+ return func(self, value)
274
+ return wrapper # return the wrapper
275
+ return decorator # return the decorator
276
+
277
+
278
+ def validate_range(min_value: Optional[float] = None,
279
+ max_value: Optional[float] = None,
280
+ min_inclusive: bool = True,
281
+ max_inclusive: bool = True,
282
+ min_attr: Optional[str] = None,
283
+ max_attr: Optional[str] = None) -> Callable:
284
+ """Decorator to validate numeric value is within specified range.
285
+
286
+ Args:
287
+ min_value (Optional[float], optional): Static minimum value. Defaults to None.
288
+ max_value (Optional[float], optional): Static maximum value. Defaults to None.
289
+ min_inclusive (bool, optional): Whether minimum is inclusive (>=) or exclusive (>). Defaults to True.
290
+ max_inclusive (bool, optional): Whether maximum is inclusive (<=) or exclusive (<). Defaults to True.
291
+ min_attr (Optional[str], optional): Attribute name for dynamic minimum (e.g., '_min'). Defaults to None.
292
+ max_attr (Optional[str], optional): Attribute name for dynamic maximum (e.g., '_max'). Defaults to None.
293
+
294
+ Raises:
295
+ ValueError: If value is outside the specified range.
296
+
297
+ Returns:
298
+ Callable: Decorated function with range validation.
299
+
300
+ Example:
301
+ # Static range
302
+ @age.setter
303
+ @validate_type(int)
304
+ @validate_range(min_value=0, max_value=150)
305
+ def age(self, val: int) -> None:
306
+ self._age = val
307
+
308
+ # Dynamic range based on other attributes
309
+ @mean.setter
310
+ @validate_type(int, float)
311
+ @validate_range(min_attr='_min', max_attr='_max')
312
+ def mean(self, val: float) -> None:
313
+ self._mean = val
314
+ """
315
+ def decorator(func: Callable) -> Callable:
316
+ @wraps(func)
317
+ def wrapper(self, value: Any) -> Any:
318
+ if value is not None:
319
+ # Check static minimum
320
+ if min_value is not None:
321
+ if min_inclusive:
322
+ if value < min_value:
323
+ _msg = f"{func.__name__} must be >= {min_value}, "
324
+ _msg += f"got {value}."
325
+ raise ValueError(_msg)
326
+ else:
327
+ if value <= min_value:
328
+ _msg = f"{func.__name__} must be > {min_value}, "
329
+ _msg += f"got {value}."
330
+ raise ValueError(_msg)
331
+
332
+ # Check static maximum
333
+ if max_value is not None:
334
+ if max_inclusive:
335
+ if value > max_value:
336
+ _msg = f"{func.__name__} must be <= {max_value}, "
337
+ _msg += f"got {value}."
338
+ raise ValueError(_msg)
339
+ else:
340
+ if value >= max_value:
341
+ _msg = f"{func.__name__} must be < {max_value}, "
342
+ _msg += f"got {value}."
343
+ raise ValueError(_msg)
344
+
345
+ # Check dynamic minimum from attribute
346
+ if min_attr and hasattr(self, min_attr):
347
+ min_val = getattr(self, min_attr)
348
+ if min_val is not None and value < min_val:
349
+ _msg = f"{func.__name__} ({value}) cannot be less than "
350
+ _msg += f"minimum ({min_val})."
351
+ raise ValueError(_msg)
352
+
353
+ # Check dynamic maximum from attribute
354
+ if max_attr and hasattr(self, max_attr):
355
+ max_val = getattr(self, max_attr)
356
+ if max_val is not None and value > max_val:
357
+ _msg = f"{func.__name__} ({value}) cannot be greater than "
358
+ _msg += f"maximum ({max_val})."
359
+ raise ValueError(_msg)
360
+
361
+ # otherwise, call the original function
362
+ return func(self, value)
363
+ return wrapper # return the wrapper
364
+ return decorator # return the decorator
365
+
366
+
367
+ def validate_pattern(pattern: Optional[Union[str, list, tuple]] = None,
368
+ allow_alnum: bool = False,
369
+ error_msg: Optional[str] = None,
370
+ examples: Optional[str] = None) -> Callable:
371
+ """Decorator to validate string matches regex pattern(s) or is alphanumeric.
372
+
373
+ This unified decorator handles:
374
+ - Single pattern matching
375
+ - Multiple pattern matching (OR logic - matches any pattern)
376
+ - Optional alphanumeric validation
377
+ - Scientific/mathematical symbols (alphanumeric OR LaTeX)
378
+
379
+ Args:
380
+ pattern (Union[str, list, tuple]): Single regex pattern string, or list/tuple of patterns to match (OR logic).
381
+ allow_alnum (bool, optional): Whether to accept alphanumeric strings. Defaults to False.
382
+ error_msg (Optional[str], optional): Custom error message (overrides default). Defaults to None.
383
+ examples (Optional[str], optional): Example strings to show in error messages. Defaults to None.
384
+
385
+ Raises:
386
+ ValueError: If value does not match any pattern and is not alphanumeric (when allowed).
387
+
388
+ Returns:
389
+ Callable: Decorated function with pattern validation.
390
+
391
+ Examples:
392
+ # Simple pattern matching
393
+ @code.setter
394
+ @validate_pattern(r'^[A-Z]\\d{3}$')
395
+ def code(self, val: str) -> None:
396
+ self._code = val
397
+
398
+ # Symbol validation (alphanumeric OR LaTeX)
399
+ from pydasa.validations.patterns import LATEX_RE
400
+
401
+ @sym.setter
402
+ @validate_type(str)
403
+ @validate_emptiness()
404
+ @validate_pattern(LATEX_RE, allow_alnum=True)
405
+ def sym(self, val: str) -> None:
406
+ self._sym = val
407
+
408
+ # Multiple patterns (match any)
409
+ @validate_pattern([r'^\\\\[a-z]+$', r'^\\d+$'])
410
+ def value(self, val: str) -> None:
411
+ self._value = val
412
+ """
413
+ # Validate decorator configuration
414
+ if pattern is None and not allow_alnum:
415
+ _msg = "Provide either 'pattern' or 'allow_alnum' must be True"
416
+ raise ValueError(_msg)
417
+
418
+ # Compile pattern(s) into list
419
+ if pattern is None:
420
+ compiled_patterns = []
421
+ elif isinstance(pattern, (list, tuple)):
422
+ compiled_patterns = [re.compile(p) for p in pattern]
423
+ else:
424
+ compiled_patterns = [re.compile(pattern)]
425
+
426
+ def decorator(func: Callable) -> Callable:
427
+ @wraps(func)
428
+ def wrapper(self, value: Any) -> Any:
429
+ if value is not None:
430
+ is_valid = False
431
+
432
+ # Check if alphanumeric (if allowed)
433
+ if allow_alnum and value.isalnum():
434
+ is_valid = True
435
+
436
+ # Check if matches any pattern
437
+ if not is_valid and compiled_patterns:
438
+ for compiled_pattern in compiled_patterns:
439
+ if compiled_pattern.match(value):
440
+ is_valid = True
441
+ break
442
+
443
+ # Raise error if validation failed
444
+ # TODO improve msg construction
445
+ if not is_valid:
446
+ if error_msg:
447
+ _msg = error_msg
448
+ elif allow_alnum and compiled_patterns:
449
+ _msg = f"{func.__name__} must be alphanumeric or match pattern. "
450
+ _msg += f"Provided: '{value}'. "
451
+ if examples:
452
+ _msg += f"Examples: {examples}"
453
+ else:
454
+ _msg += "Examples: 'V', 'd', '\\\\Pi_{{0}}', '\\\\rho'."
455
+ elif allow_alnum:
456
+ _msg = f"{func.__name__} must be alphanumeric. "
457
+ _msg += f"Provided: '{value}'."
458
+ elif len(compiled_patterns) == 1:
459
+ _msg = f"{func.__name__} must match pattern. "
460
+ _msg += f"Provided: {repr(value)}."
461
+ elif len(compiled_patterns) > 1:
462
+ _msg = f"{func.__name__} must match one of {len(compiled_patterns)} patterns. "
463
+ _msg += f"Provided: {repr(value)}."
464
+ else:
465
+ _msg = f"{func.__name__} validation failed for: {repr(value)}."
466
+
467
+ raise ValueError(_msg)
468
+
469
+ # otherwise, call the original function
470
+ return func(self, value)
471
+ return wrapper # return the wrapper
472
+ return decorator # return the decorator
473
+
474
+
475
+ def validate_custom(validator_func: Callable[[Any, Any], None]) -> Callable:
476
+ """*validate_custom()* Decorator for custom validation logic. Allows implementing custom validation logic by providing a validator function.
477
+
478
+ The validator function should raise ValueError if validation fails.
479
+ NOTE: this is too abstract and should be used sparingly.
480
+
481
+ Args:
482
+ validator_func (Callable[[Any, Any], None]): Function(self, value) that raises ValueError if invalid.
483
+
484
+ Raises:
485
+ ValueError: If custom validator function raises ValueError.
486
+
487
+ Returns:
488
+ Callable: Decorated function with custom validation.
489
+
490
+ Example:
491
+ def check_range_consistency(self, value):
492
+ '''Ensure minimum does not exceed maximum.'''
493
+ if value is not None and self._max is not None and value > self._max:
494
+ raise ValueError(f"min {value} > max {self._max}")
495
+
496
+ @min.setter
497
+ @validate_type(int, float)
498
+ @validate_custom(check_range_consistency)
499
+ def min(self, val: float) -> None:
500
+ self._min = val
501
+ """
502
+ def decorator(func: Callable) -> Callable:
503
+ @wraps(func)
504
+ def wrapper(self, value: Any) -> Any:
505
+ # Run custom validator
506
+ validator_func(self, value)
507
+ # otherwise, call the original function
508
+ return func(self, value)
509
+ return wrapper # return the wrapper
510
+ return decorator # return the decorator
@@ -0,0 +1,100 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Module error.py
4
+ ===========================================
5
+
6
+ General error handling module/function for the PyDASA Data Structures and Algorithms in PyDASA package.
7
+ """
8
+ # native python modules
9
+ import inspect
10
+ from typing import Any
11
+ # custom modules
12
+ # import global variables
13
+
14
+
15
+ def handle_error(ctx: str, func: str, exc: Exception) -> None:
16
+ """*handle_error()* generic function to handle errors iacross the whole PyDASA library.
17
+
18
+ Args:
19
+ ctx (str): The context (e.g., package/class) where the error occurred.
20
+ func (str): The name of the function or method where the error occurred.
21
+ exc (Exception): The exception that was raised.
22
+
23
+ Raises:
24
+ TypeError: If the context is not a string.
25
+ TypeError: If the function name is not a string.
26
+ TypeError: If the exception is not an instance of Exception.
27
+ type: If the error message is not a string.
28
+ """
29
+ # Validate the context
30
+ if not isinstance(ctx, str):
31
+ _msg = f"Invalid context: {ctx}. Context must be a string."
32
+ raise TypeError(_msg)
33
+
34
+ # Validate the function name
35
+ if not isinstance(func, str):
36
+ _msg = f"Invalid function name: {func}. "
37
+ _msg += "Function name must be a string."
38
+ raise TypeError(_msg)
39
+
40
+ # Validate the exception
41
+ if not isinstance(exc, Exception):
42
+ _msg = f"Invalid exception: {exc}. "
43
+ _msg += "Exception must be an instance of Exception."
44
+ raise TypeError(_msg)
45
+
46
+ # Format and raise the error with additional context
47
+ _err_msg = f"Error in {ctx}.{func}: {exc}"
48
+ raise type(exc)(_err_msg).with_traceback(exc.__traceback__)
49
+
50
+
51
+ def inspect_var(var: Any) -> str:
52
+ """*inspect_var() inspect a variable an gets its name in the source code.
53
+
54
+ Args:
55
+ var (Any): The variable to inspect.
56
+
57
+ Returns:
58
+ str: The name of the variable.
59
+
60
+ Raises:
61
+ ValueError: If the variable name cannot be found in the current scope.
62
+ """
63
+ # Get the current frame
64
+ frame = inspect.currentframe()
65
+ try:
66
+ # Check if frame exists
67
+ if frame is None:
68
+ return "<unknown>"
69
+
70
+ # Get the caller's frame
71
+ caller_frame = frame.f_back
72
+
73
+ # Check if caller_frame exists
74
+ if caller_frame is None:
75
+ return "<unknown>"
76
+
77
+ # Get local variables from caller's frame
78
+ caller_locals = caller_frame.f_locals
79
+
80
+ # Search for the variable in caller's locals
81
+ for name, value in caller_locals.items():
82
+ if value is var:
83
+ return name
84
+
85
+ # If not found in locals, check globals
86
+ caller_globals = caller_frame.f_globals
87
+ for name, value in caller_globals.items():
88
+ if value is var:
89
+ return name
90
+
91
+ return "<unknown>"
92
+
93
+ except Exception as e:
94
+ # If introspection fails for any reason, return unknown
95
+ _msg = f"Could not inspect variable: {var}"
96
+ _msg += f" due to error: {e}"
97
+ raise ValueError(_msg)
98
+ finally:
99
+ # Clean up frame references to avoid reference cycles
100
+ del frame
@@ -0,0 +1,32 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Module patterns.py
4
+ ===========================================
5
+
6
+ Regex patterns for validation and parsing in PyDASA.
7
+
8
+ Contains:
9
+ - LaTeX validation patterns
10
+ - FDU (Fundamental Dimensional Unit) matching patterns
11
+ - Default and working pattern sets for dimensional analysis
12
+ """
13
+
14
+ # LaTeX Patterns
15
+ # Allow valid LaTeX strings starting with a backslash or alphanumeric strings
16
+
17
+
18
+ # NOTE: OG REGEX!
19
+ # LATEX_RE: str = r"([a-zA-Z]+)(?:_\{\d+\})?"
20
+ # :attr: LATEX_RE
21
+ LATEX_RE: str = r"\\?[a-zA-Z]+(?:_\{\d+\})?"
22
+ """
23
+ LaTeX regex pattern to match LaTeX symbols (e.g., '\\alpha', '\\beta_{1}') in *PyDASA*.
24
+ """
25
+
26
+ # NOTE: OG REGEX!
27
+ # DFLT_POW_RE: str = r"\-?\d+" # r'\^(-?\d+)'
28
+ # :attr: DFLT_POW_RE
29
+ DFLT_POW_RE: str = r"\-?\d+"
30
+ """
31
+ Default regex to match FDUs with exponents (e.g., 'M*L^-1*T^-2' to 'M^(1)*L^(-1)*T^(-2)').
32
+ """
@@ -0,0 +1 @@
1
+ # TODO deprecate after migration