hms-commander 0.1.0__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.
@@ -0,0 +1,150 @@
1
+ """
2
+ Decorators for hms-commander library.
3
+
4
+ Provides decorators for logging and input standardization for HMS file operations.
5
+ """
6
+
7
+ from functools import wraps
8
+ from pathlib import Path
9
+ from typing import Union
10
+ import logging
11
+ import inspect
12
+
13
+ # Import log_call from LoggingConfig (single source of truth)
14
+ from .LoggingConfig import log_call
15
+
16
+ # Re-export for backwards compatibility
17
+ __all__ = ['log_call', 'standardize_path']
18
+
19
+
20
+ def standardize_path(file_type: str = 'hms'):
21
+ """
22
+ Decorator to standardize input paths for HMS file operations.
23
+
24
+ This decorator processes various input types and converts them to a Path object
25
+ pointing to the correct HMS file. It handles the following input types:
26
+ - pathlib.Path objects
27
+ - Strings (file paths)
28
+
29
+ The decorator also manages HMS object references.
30
+
31
+ Args:
32
+ file_type (str): Specifies the type of HMS file to look for:
33
+ - 'hms': Project file (.hms)
34
+ - 'basin': Basin model file (.basin)
35
+ - 'met': Meteorologic model file (.met)
36
+ - 'control': Control specification file (.control)
37
+ - 'gage': Time-series gage file (.gage)
38
+ - 'grid': Grid file (.grid)
39
+ - 'pdata': Paired data file (.pdata)
40
+ - 'run': Run file (.run)
41
+
42
+ Returns:
43
+ A decorator that wraps the function to standardize its input to a Path object.
44
+ """
45
+ def decorator(func):
46
+ @wraps(func)
47
+ def wrapper(*args, **kwargs):
48
+ logger = logging.getLogger(func.__module__)
49
+
50
+ # Check if the function expects a path parameter
51
+ sig = inspect.signature(func)
52
+ param_names = list(sig.parameters.keys())
53
+
54
+ # Handle both static method calls and regular function calls
55
+ if args and isinstance(args[0], type):
56
+ # Static method call, remove the class argument
57
+ args = args[1:]
58
+
59
+ # Get file_input from kwargs if provided with file_path key, or take first positional arg
60
+ file_input = kwargs.pop('file_path', None) if 'file_path' in kwargs else (args[0] if args else None)
61
+
62
+ # hms_object is always keyword-only, never in args
63
+ hms_object = kwargs.pop('hms_object', None)
64
+
65
+ # Get global hms object only if hms_object not provided
66
+ # Use lazy import to avoid circular dependency during HmsPrj initialization
67
+ hms_obj = hms_object
68
+ if hms_obj is None:
69
+ try:
70
+ from .HmsPrj import hms
71
+ hms_obj = hms
72
+ except ImportError:
73
+ hms_obj = None
74
+
75
+ # If no file_input provided, return the function unmodified
76
+ if file_input is None:
77
+ return func(*args, **kwargs)
78
+
79
+ file_path = None
80
+
81
+ # Clean and normalize string inputs
82
+ if isinstance(file_input, str):
83
+ # Clean the string (remove extra whitespace, normalize path separators)
84
+ file_input = file_input.strip()
85
+
86
+ # Check if it's a raw file path that exists
87
+ try:
88
+ test_path = Path(file_input)
89
+ if test_path.is_file():
90
+ file_path = test_path
91
+ logger.info(f"Using file from direct string path: {file_path}")
92
+ except Exception as e:
93
+ logger.debug(f"Error converting string to path: {str(e)}")
94
+
95
+ # If a valid path wasn't created from string processing, continue with normal flow
96
+ if file_path is None:
97
+ # If file_input is already a Path and exists, use it directly
98
+ if isinstance(file_input, Path) and file_input.is_file():
99
+ file_path = file_input
100
+ logger.info(f"Using existing Path object file: {file_path}")
101
+
102
+ # Handle string inputs that are file names (not full paths) from HMS project
103
+ elif isinstance(file_input, str) and hms_obj is not None:
104
+ try:
105
+ hms_obj.check_initialized()
106
+
107
+ # Look up file in appropriate DataFrame based on file_type
108
+ if file_type == 'basin':
109
+ matching = hms_obj.basin_df[hms_obj.basin_df['name'] == file_input]
110
+ if not matching.empty:
111
+ file_path = Path(matching.iloc[0]['full_path'])
112
+
113
+ elif file_type == 'met':
114
+ matching = hms_obj.met_df[hms_obj.met_df['name'] == file_input]
115
+ if not matching.empty:
116
+ file_path = Path(matching.iloc[0]['full_path'])
117
+
118
+ elif file_type == 'control':
119
+ matching = hms_obj.control_df[hms_obj.control_df['name'] == file_input]
120
+ if not matching.empty:
121
+ file_path = Path(matching.iloc[0]['full_path'])
122
+
123
+ elif file_type == 'run':
124
+ matching = hms_obj.run_df[hms_obj.run_df['name'] == file_input]
125
+ if not matching.empty:
126
+ file_path = Path(matching.iloc[0]['full_path'])
127
+
128
+ except Exception as e:
129
+ logger.debug(f"Error looking up file in HMS project: {str(e)}")
130
+
131
+ # Final verification that the path exists
132
+ if file_path is None or not file_path.exists():
133
+ error_msg = f"HMS {file_type} file not found: {file_input}"
134
+ logger.error(error_msg)
135
+ raise FileNotFoundError(error_msg)
136
+
137
+ logger.info(f"Final validated file path: {file_path}")
138
+
139
+ # Pass all original arguments and keywords, replacing file_input with standardized file_path
140
+ # If the original input was positional, replace the first argument
141
+ if args and 'file_path' not in kwargs:
142
+ new_args = (file_path,) + args[1:]
143
+ else:
144
+ new_args = args
145
+ kwargs['file_path'] = file_path
146
+
147
+ return func(*new_args, **kwargs)
148
+
149
+ return wrapper
150
+ return decorator