hydrodl2 1.3.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.
hydrodl2/__init__.py ADDED
@@ -0,0 +1,122 @@
1
+ # src/hydrodl2/__init__.py
2
+ import logging
3
+ import os
4
+ import sys
5
+ from datetime import datetime
6
+ from importlib.resources import files
7
+ from pathlib import Path
8
+
9
+ from platformdirs import user_config_dir
10
+
11
+ from hydrodl2._version import __version__
12
+ from hydrodl2.api import available_models, available_modules, load_model, load_module
13
+
14
+ log = logging.getLogger('hydrodl2')
15
+
16
+ # In case setuptools scm says version is 0.0.0
17
+ assert not __version__.startswith('0.0.0')
18
+
19
+ __all__ = [
20
+ '__version__',
21
+ 'available_models',
22
+ 'available_modules',
23
+ 'load_model',
24
+ 'load_module',
25
+ ]
26
+
27
+
28
+ def _check_license_agreement():
29
+ """Checks if user has agreed to package license and prompts if not."""
30
+ package_name = 'hydrodl2'
31
+
32
+ config_dir = Path(user_config_dir(package_name))
33
+ agreement_file = config_dir / '.license_status'
34
+
35
+ model_classes = available_models()
36
+
37
+ if not agreement_file.exists():
38
+ print(f"\n[----- {package_name} LICENSE AGREEMENT -----]")
39
+
40
+ try:
41
+ # Find and read LICENSE file
42
+ license_path = files(package_name).parent.parent.joinpath("LICENSE")
43
+ license = license_path.read_text(encoding="utf-8")
44
+ print(license)
45
+ except FileNotFoundError:
46
+ # Fallback in case the LICENSE file wasn't packaged correctly
47
+ print(
48
+ "\n|> Error locating License. Showing summary <|\n"
49
+ "By using this software, you agree to the terms specified \n"
50
+ "in the Non-Commercial Software License Agreement: \n"
51
+ "\nhttps://github.com/mhpi/hydrodl2/blob/master/LICENSE \n"
52
+ "\n'hydrodl2' models are free for non-commercial use. \n"
53
+ "Prior authorization must be obtained for commercial \n"
54
+ "use. For further details, please contact the Pennsylvania \n"
55
+ "State University Office of Technology Management at \n"
56
+ "814.865.6277 or otminfo@psu.edu.\n"
57
+ )
58
+
59
+ print("\nThis agreement applies to all named models in this package:\n")
60
+
61
+ if model_classes: # Avoid error if model_classes is empty
62
+ max_len = max(len(model) for model in model_classes) + 2
63
+ else:
64
+ max_len = 0
65
+
66
+ for model in model_classes:
67
+ model_string = ''
68
+ for submodel in model_classes[model]:
69
+ model_string += str(submodel) + ', '
70
+ print(f"-> {model:<{max_len}}: {model_string[:-3]}")
71
+
72
+ print("-" * 40)
73
+
74
+ try:
75
+ response = input("Do you agree to these terms? Type 'Yes' to continue: ")
76
+ except EOFError:
77
+ # If we get here, it means we're in an environment that can't take
78
+ # input -- default to no agreement.
79
+ print("\n[!] No terminal detected. Skipping license prompt.")
80
+ return
81
+
82
+ if response.strip().lower() in ['yes', 'y']:
83
+ try:
84
+ config_dir.mkdir(parents=True, exist_ok=True)
85
+ agreement_file.write_text(
86
+ f"accepted_on = {datetime.now().isoformat()}Z\nversion = 1\n",
87
+ encoding="utf-8",
88
+ )
89
+ log.warning(
90
+ f"License accepted. Agreement written to {agreement_file}\n"
91
+ )
92
+ except OSError as e:
93
+ log.warning(f"Failed to save agreement file {agreement_file}: {e}")
94
+ print(
95
+ "You may need to run with administrator privileges to avoid "
96
+ "repeating this process at runtime.",
97
+ )
98
+ else:
99
+ print("\n>| License agreement not accepted. Exiting. <|")
100
+ raise SystemExit(1)
101
+
102
+
103
+ def _should_skip_license():
104
+ """Returns True if the license check should be bypassed."""
105
+ # 1. Check if we are in a Non-Interactive shell (No user to type 'Yes')
106
+ if not sys.stdin.isatty():
107
+ return True
108
+
109
+ # 2. Check for common 'Silent' or 'CI' flags
110
+ if os.environ.get('CI') or os.environ.get('NGEN_SILENT'):
111
+ return True
112
+
113
+ # 3. Check for the Docker flag (as a fallback)
114
+ if os.path.exists('/.dockerenv'):
115
+ return True
116
+
117
+ return False
118
+
119
+
120
+ # This only runs once when package is first imported.
121
+ if not _should_skip_license():
122
+ _check_license_agreement()
hydrodl2/_version.py ADDED
@@ -0,0 +1,34 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
12
+
13
+ TYPE_CHECKING = False
14
+ if TYPE_CHECKING:
15
+ from typing import Tuple
16
+ from typing import Union
17
+
18
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
20
+ else:
21
+ VERSION_TUPLE = object
22
+ COMMIT_ID = object
23
+
24
+ version: str
25
+ __version__: str
26
+ __version_tuple__: VERSION_TUPLE
27
+ version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
30
+
31
+ __version__ = version = '1.3.0'
32
+ __version_tuple__ = version_tuple = (1, 3, 0)
33
+
34
+ __commit_id__ = commit_id = None
@@ -0,0 +1,3 @@
1
+ from .methods import available_models, available_modules, load_model, load_module
2
+
3
+ __all__ = ['available_models', 'available_modules', 'load_model', 'load_module']
@@ -0,0 +1,144 @@
1
+ """
2
+ Note: If adding new public methods, please add them to __all__
3
+ at the top of the file and in api/__init__.py.
4
+ """
5
+
6
+ import importlib.util
7
+ import logging
8
+ import os
9
+ import re
10
+
11
+ from torch.nn import Module
12
+
13
+ from hydrodl2.core.utils import _get_dir, get_model_dirs, get_model_files
14
+
15
+ log = logging.getLogger("hydrodl2")
16
+
17
+
18
+ def available_models() -> dict[str, list[str]]:
19
+ """Identify and list all available models in hydroDL2.
20
+
21
+ Returns
22
+ -------
23
+ list
24
+ A list of available models.
25
+ """
26
+ # Path to the models directory
27
+ model_dir = _get_dir('models')
28
+ models = {}
29
+
30
+ dirs, _ = get_model_dirs(model_dir)
31
+ for dir in dirs:
32
+ _, file_names = get_model_files(dir)
33
+ models[dir.name] = file_names
34
+
35
+ return models
36
+
37
+
38
+ def _list_available_models() -> list[str]:
39
+ """List all available models in hydroDL2 without the dict nesting
40
+ of available_models().
41
+
42
+ Returns
43
+ -------
44
+ list
45
+ A list of available models.
46
+ """
47
+ model_dir = _get_dir('models')
48
+ models = []
49
+ dirs, _ = get_model_dirs(model_dir)
50
+ for dir in dirs:
51
+ _, file_names = get_model_files(dir)
52
+ for file in file_names:
53
+ models.append(file)
54
+
55
+ return models
56
+
57
+
58
+ def available_modules() -> dict[str, list[str]]:
59
+ """Identify and list all available modules in the hydroDL2.
60
+
61
+ Returns
62
+ -------
63
+ list
64
+ A list of available modules.
65
+ """
66
+ # Path to the modules directory
67
+ model_dir = _get_dir('modules')
68
+ modules = {}
69
+
70
+ dirs, _ = get_model_dirs(model_dir)
71
+ for dir in dirs:
72
+ _, file_names = get_model_files(dir)
73
+ modules[dir.name] = file_names
74
+
75
+ return modules
76
+
77
+
78
+ def load_model(model: str, ver_name: str = None) -> Module:
79
+ """Load a model from the models directory.
80
+
81
+ Each model file in `models/` directory should only contain one model class.
82
+
83
+ Parameters
84
+ ----------
85
+ model
86
+ The model name.
87
+ ver_name
88
+ The version name (class) of the model to load within the model file.
89
+
90
+ Returns
91
+ -------
92
+ Module
93
+ The uninstantiated model.
94
+ """
95
+ parent_dir = _get_dir('models')
96
+
97
+ if ver_name is None:
98
+ ver_name = model # Default to the model name if no version is specified
99
+
100
+ # Construct file path
101
+ model = re.sub(
102
+ r'([a-z])([A-Z])', r'\1_\2', model
103
+ ).lower() # Convert camelCase to snake_case
104
+ model_dir = model.split('_')[
105
+ 0
106
+ ].lower() # Model class name is first word in snake_case
107
+ model_subpath = os.path.join(model_dir, f'{model}.py')
108
+
109
+ # Path to the module file in the model directory
110
+ source = os.path.join(parent_dir, model_subpath)
111
+
112
+ # Load the model dynamically as a module
113
+ try:
114
+ spec = importlib.util.spec_from_file_location(model, source)
115
+ module = importlib.util.module_from_spec(spec)
116
+ spec.loader.exec_module(module)
117
+ except ImportError as e:
118
+ raise ImportError(f"Model '{model}' not found.") from e
119
+
120
+ # Retrieve version name if possible, otherwise get first class in module
121
+ try:
122
+ cls = getattr(module, ver_name)
123
+ except AttributeError as e:
124
+ # Find first class in module (NOTE: not guaranteed accurate)
125
+ classes = [
126
+ attr
127
+ for attr in dir(module)
128
+ if isinstance(getattr(module, attr), type) and attr != 'Any'
129
+ ]
130
+ if not classes:
131
+ raise ImportError(f"Model version '{model}' not found.") from e
132
+
133
+ log.warning(
134
+ f"Model class '{ver_name}' not found in module '{module.__file__}'. "
135
+ f"Falling back to the first available: '{classes[0]}'."
136
+ )
137
+ cls = getattr(module, classes[0])
138
+
139
+ return cls
140
+
141
+
142
+ def load_module():
143
+ """Load a module from the modules directory."""
144
+ raise NotImplementedError("This function is not yet implemented.")
@@ -0,0 +1,11 @@
1
+ from .fdj import finite_difference_jacobian_p
2
+ from .uh_routing import uh_conv, uh_gamma
3
+ from .utils import change_param_range, param_bounds_2d
4
+
5
+ __all__ = [
6
+ 'change_param_range',
7
+ 'param_bounds_2d',
8
+ 'uh_gamma',
9
+ 'uh_conv',
10
+ 'finite_difference_jacobian_p',
11
+ ]