sunholo 0.71.28__py3-none-any.whl → 0.72.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.
sunholo/gcs/__init__.py CHANGED
@@ -1 +1 @@
1
- from .download_url import construct_download_link, get_bytes_from_gcs
1
+ from .download_url import construct_download_link, get_bytes_from_gcs, get_image_from_gcs
@@ -14,12 +14,27 @@ except ImportError:
14
14
 
15
15
  from ..logging import log
16
16
  from ..utils.gcp import is_running_on_gcp
17
+ from io import BytesIO
18
+ try:
19
+ from PIL import Image
20
+ except ImportError:
21
+ Image = None
17
22
 
18
23
  gcs_credentials = None
19
24
  project_id = None
20
25
  gcs_client = None
21
26
  gcs_bucket_cache = {}
22
27
 
28
+ def get_image_from_gcs(gs_uri: str):
29
+ """Converts image bytes from GCS to a PIL Image object."""
30
+ image_bytes = get_bytes_from_gcs(gs_uri)
31
+ if not Image:
32
+ raise ImportError('Could not import PIL (pillow) - install via `pip install sunholo[gcp]`')
33
+ try:
34
+ img = Image.open(BytesIO(image_bytes))
35
+ return img
36
+ except IOError as e:
37
+ raise ValueError("Unable to open image from bytes:", e)
23
38
 
24
39
  def get_bytes_from_gcs(gs_uri):
25
40
  """
sunholo/utils/__init__.py CHANGED
@@ -1 +1,2 @@
1
- from .config import load_config_key, load_config
1
+ from .config import load_config_key, load_config
2
+ from .config_class import ConfigManager
@@ -0,0 +1,207 @@
1
+ import os
2
+ import json
3
+ import yaml
4
+ from datetime import datetime, timedelta
5
+ from collections import defaultdict
6
+ from .timedelta import format_timedelta
7
+
8
+ class ConfigManager:
9
+ def __init__(self, vector_name: str):
10
+ """
11
+ Initialize the ConfigManager with a vector name.
12
+
13
+ Args:
14
+ vector_name (str): The name of the vector in the configuration files.
15
+
16
+ Example:
17
+ ```python
18
+ # Usage example:
19
+ config = ConfigManager("myVector")
20
+ agent = config.vacConfig("agent")
21
+ ```
22
+ """
23
+ self.vector_name = vector_name
24
+ self.config_cache = {}
25
+ self.config_folder = os.getenv("VAC_CONFIG_FOLDER", os.getcwd())
26
+ self.local_config_folder = os.path.join(os.getcwd(), "config")
27
+ self.configs_by_kind = self.load_all_configs()
28
+
29
+ def load_all_configs(self):
30
+ """
31
+ Load all configuration files from the specified directories into a dictionary.
32
+ Caching is used to avoid reloading files within a 5-minute window.
33
+
34
+ Returns:
35
+ dict: A dictionary of configurations grouped by their 'kind' key.
36
+ """
37
+ from ..logging import log
38
+
39
+ log.debug(f"Loading all configs from folder: {self.config_folder} and local folder: {self.local_config_folder}")
40
+ global_configs_by_kind = self._load_configs_from_folder(self.config_folder)
41
+ local_configs_by_kind = self._load_configs_from_folder(self.local_config_folder)
42
+
43
+ # Merge local configs into global configs
44
+ for kind, local_config in local_configs_by_kind.items():
45
+ if kind in global_configs_by_kind:
46
+ global_configs_by_kind[kind] = self._merge_dicts(global_configs_by_kind[kind], local_config)
47
+ else:
48
+ global_configs_by_kind[kind] = local_config
49
+
50
+ return global_configs_by_kind
51
+
52
+ def _load_configs_from_folder(self, folder):
53
+ """
54
+ Load all configuration files from a specific folder into a dictionary.
55
+
56
+ Args:
57
+ folder (str): The path of the folder to load configurations from.
58
+
59
+ Returns:
60
+ dict: A dictionary of configurations grouped by their 'kind' key.
61
+ """
62
+ from ..logging import log
63
+
64
+ configs_by_kind = defaultdict(dict)
65
+ current_time = datetime.now()
66
+
67
+ for filename in os.listdir(folder):
68
+ if filename in ["cloudbuild.yaml", "cloud_run_urls.json"]:
69
+ continue
70
+ if filename.endswith(('.yaml', '.yml', '.json')):
71
+ config_file = os.path.join(folder, filename)
72
+ if filename in self.config_cache:
73
+ cached_config, cache_time = self.config_cache[filename]
74
+ time_to_recache = (current_time - cache_time)
75
+ if time_to_recache < timedelta(minutes=5):
76
+ config = cached_config
77
+ else:
78
+ config = self._reload_config_file(config_file, filename, folder == self.local_config_folder)
79
+ else:
80
+ config = self._reload_config_file(config_file, filename, folder == self.local_config_folder)
81
+ kind = config.get('kind')
82
+ if kind:
83
+ configs_by_kind[kind] = config
84
+ else:
85
+ log.warning(f"No 'kind' found in {filename}")
86
+ return configs_by_kind
87
+
88
+ def _reload_config_file(self, config_file, filename, is_local=False):
89
+ """
90
+ Helper function to load a config file and update the cache.
91
+
92
+ Args:
93
+ config_file (str): The path to the configuration file.
94
+ filename (str): The name of the configuration file.
95
+ is_local (bool): Indicates if the config file is from the local folder.
96
+
97
+ Returns:
98
+ dict: The loaded configuration.
99
+ """
100
+ from ..logging import log
101
+ with open(config_file, 'r') as file:
102
+ if filename.endswith('.json'):
103
+ config = json.load(file)
104
+ else:
105
+ config = yaml.safe_load(file)
106
+ self.config_cache[filename] = (config, datetime.now())
107
+ log.debug(f"Loaded and cached {config_file}")
108
+ if is_local:
109
+ log.warning(f"Local configuration override for {filename}")
110
+ return config
111
+
112
+ def _check_and_reload_configs(self):
113
+ """
114
+ Check if configurations are older than 5 minutes and reload if necessary.
115
+ """
116
+ current_time = datetime.now()
117
+ for filename, (config, cache_time) in list(self.config_cache.items()):
118
+ if (current_time - cache_time) >= timedelta(minutes=5):
119
+ config_file_main = os.path.join(self.config_folder, filename)
120
+ config_file_local = os.path.join(self.local_config_folder, filename)
121
+ if os.path.exists(config_file_local):
122
+ self._reload_config_file(config_file_local, filename, is_local=True)
123
+ if os.path.exists(config_file_main):
124
+ self._reload_config_file(config_file_main, filename, is_local=False)
125
+ self.configs_by_kind = self.load_all_configs()
126
+
127
+ def _merge_dicts(self, dict1, dict2):
128
+ """
129
+ Recursively merge two dictionaries. Local values in dict2 will overwrite global values in dict1.
130
+
131
+ Args:
132
+ dict1 (dict): The global dictionary.
133
+ dict2 (dict): The local dictionary.
134
+
135
+ Returns:
136
+ dict: The merged dictionary.
137
+ """
138
+ for key, value in dict2.items():
139
+ if isinstance(value, dict) and key in dict1 and isinstance(dict1[key], dict):
140
+ dict1[key] = self._merge_dicts(dict1[key], value)
141
+ else:
142
+ dict1[key] = value
143
+ return dict1
144
+
145
+ def vacConfig(self, key: str):
146
+ """
147
+ Fetch a key from 'vacConfig' kind configuration.
148
+
149
+ Args:
150
+ key (str): The key to fetch from the configuration.
151
+
152
+ Returns:
153
+ str: The value associated with the specified key.
154
+ """
155
+ self._check_and_reload_configs()
156
+ config = self.configs_by_kind.get('vacConfig')
157
+ if not config:
158
+ return None
159
+ if self.vector_name == 'global':
160
+ return config.get(key)
161
+ vac = config['vac']
162
+
163
+ vac_config = vac.get(self.vector_name)
164
+ if not vac_config:
165
+ return None
166
+ return vac_config.get(key)
167
+
168
+ def promptConfig(self, key: str):
169
+ """
170
+ Fetch a key from 'promptConfig' kind configuration.
171
+
172
+ Args:
173
+ key (str): The key to fetch from the configuration.
174
+
175
+ Returns:
176
+ str: The value associated with the specified key.
177
+ """
178
+ self._check_and_reload_configs()
179
+ config = self.configs_by_kind.get('promptConfig')
180
+ if not config:
181
+ return None
182
+ prompts = config['prompts']
183
+ prompt_for_vector_name = prompts.get(self.vector_name)
184
+ if not prompt_for_vector_name:
185
+ return None
186
+ return prompt_for_vector_name.get(key)
187
+
188
+ def agentConfig(self, key: str):
189
+ """
190
+ Fetch a key from 'agentConfig' kind configuration.
191
+
192
+ Args:
193
+ key (str): The key to fetch from the configuration.
194
+
195
+ Returns:
196
+ str: The value associated with the specified key.
197
+ """
198
+ self._check_and_reload_configs()
199
+ config = self.configs_by_kind.get('agentConfig')
200
+ if not config:
201
+ return None
202
+ agents = config.get('agents')
203
+ if key in agents:
204
+ return agents[key]
205
+ else:
206
+ return agents.get("default")
207
+
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sunholo
3
- Version: 0.71.28
3
+ Version: 0.72.0
4
4
  Summary: Large Language Model DevOps - a package to help deploy LLMs to the Cloud.
5
5
  Home-page: https://github.com/sunholo-data/sunholo-py
6
- Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.71.28.tar.gz
6
+ Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.72.0.tar.gz
7
7
  Author: Holosun ApS
8
8
  Author-email: multivac@sunholo.com
9
9
  License: Apache License, Version 2.0
@@ -56,6 +56,7 @@ Requires-Dist: langchain-anthropic >=0.1.13 ; extra == 'all'
56
56
  Requires-Dist: langfuse ; extra == 'all'
57
57
  Requires-Dist: pg8000 ; extra == 'all'
58
58
  Requires-Dist: pgvector ; extra == 'all'
59
+ Requires-Dist: pillow ; extra == 'all'
59
60
  Requires-Dist: playwright ; extra == 'all'
60
61
  Requires-Dist: psycopg2-binary ; extra == 'all'
61
62
  Requires-Dist: pypdf ; extra == 'all'
@@ -82,6 +83,8 @@ Requires-Dist: psycopg2-binary ; extra == 'database'
82
83
  Requires-Dist: lancedb ; extra == 'database'
83
84
  Requires-Dist: tantivy ; extra == 'database'
84
85
  Provides-Extra: gcp
86
+ Requires-Dist: google-api-python-client ; extra == 'gcp'
87
+ Requires-Dist: google-cloud-alloydb-connector[pg8000] ; extra == 'gcp'
85
88
  Requires-Dist: google-auth-httplib2 ; extra == 'gcp'
86
89
  Requires-Dist: google-auth-oauthlib ; extra == 'gcp'
87
90
  Requires-Dist: google-cloud-aiplatform ; extra == 'gcp'
@@ -95,8 +98,7 @@ Requires-Dist: google-cloud-discoveryengine ; extra == 'gcp'
95
98
  Requires-Dist: google-generativeai >=0.7.1 ; extra == 'gcp'
96
99
  Requires-Dist: langchain-google-genai >=1.0.5 ; extra == 'gcp'
97
100
  Requires-Dist: langchain-google-alloydb-pg >=0.2.2 ; extra == 'gcp'
98
- Requires-Dist: google-api-python-client ; extra == 'gcp'
99
- Requires-Dist: google-cloud-alloydb-connector[pg8000] ; extra == 'gcp'
101
+ Requires-Dist: pillow ; extra == 'gcp'
100
102
  Provides-Extra: http
101
103
  Requires-Dist: fastapi ; extra == 'http'
102
104
  Requires-Dist: flask ; extra == 'http'
@@ -66,9 +66,9 @@ sunholo/discovery_engine/create_new.py,sha256=7oZG78T6lW0EspRzlo7-qRyXFSuFxDn2df
66
66
  sunholo/discovery_engine/discovery_engine_client.py,sha256=YYsFeaW41l8jmWCruQnYxJGKEYBZ7dduTBDhdxI63hQ,17719
67
67
  sunholo/embedder/__init__.py,sha256=sI4N_CqgEVcrMDxXgxKp1FsfsB4FpjoXgPGkl4N_u4I,44
68
68
  sunholo/embedder/embed_chunk.py,sha256=d_dIzeNF630Q0Ar-u1hxos60s0tLIImJccAvuo_LTIw,6814
69
- sunholo/gcs/__init__.py,sha256=NBribFzdXMphwIEABP3YaQrbb54R3Kiz-To6MdVy0Kc,70
69
+ sunholo/gcs/__init__.py,sha256=SZvbsMFDko40sIRHTHppA37IijvJTae54vrhooEF5-4,90
70
70
  sunholo/gcs/add_file.py,sha256=Nj75z1MH9fp3fvwd1BVLcHNFm0CVCR2uS4uByThos-o,6924
71
- sunholo/gcs/download_url.py,sha256=N-fUmZA6HMCiOkAusY-o04ihs_0fpt1quyifv2c_dYs,5922
71
+ sunholo/gcs/download_url.py,sha256=Kg9EdPnc---YSUTAZEdzJeITjDtQSLMYwb4uiU9LhIQ,6440
72
72
  sunholo/gcs/metadata.py,sha256=C9sMPsHsq1ETetdQCqB3EBs3Kws8b8QHS9L7ei_v5aw,891
73
73
  sunholo/langfuse/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
74
74
  sunholo/langfuse/callback.py,sha256=CTaos8sYcrga949BG6lIZ4I62DiiQSHxwz5re9XjDWQ,1677
@@ -98,10 +98,11 @@ sunholo/summarise/__init__.py,sha256=MZk3dblUMODcPb1crq4v-Z508NrFIpkSWNf9FIO8BcU
98
98
  sunholo/summarise/summarise.py,sha256=C3HhjepTjUhUC8FLk4jMQIBvq1BcORniwuTFHjPVhVo,3784
99
99
  sunholo/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
100
100
  sunholo/tools/web_browser.py,sha256=NgsAeVcndl-vMAbAfIzDJ8eRfCh5LDZan16OCNEKFmI,7094
101
- sunholo/utils/__init__.py,sha256=G11nN_6ATjxpuMfG_BvcUr9UU8onPIgkpTK6CjOcbr8,48
101
+ sunholo/utils/__init__.py,sha256=Hv02T5L2zYWvCso5hzzwm8FQogwBq0OgtUbN_7Quzqc,89
102
102
  sunholo/utils/api_key.py,sha256=Ct4bIAQZxzPEw14hP586LpVxBAVi_W9Serpy0BK-7KI,244
103
103
  sunholo/utils/big_context.py,sha256=gJIP7_ZL-YSLhOMq8jmFTMqH1wq8eB1NK7oKPeZAq2s,5578
104
104
  sunholo/utils/config.py,sha256=5lzO9CkLpDslp66ZwSBC_95aA1FQs-zpiOLi5YaYWbM,8907
105
+ sunholo/utils/config_class.py,sha256=48zEohztqTb13PPYK9SLqWQtrperan-kCVizdhKg8_g,7647
105
106
  sunholo/utils/config_schema.py,sha256=Wv-ncitzljOhgbDaq9qnFqH5LCuxNv59dTGDWgd1qdk,4189
106
107
  sunholo/utils/gcp.py,sha256=uueODEpA-P6O15-t0hmcGC9dONLO_hLfzSsSoQnkUss,4854
107
108
  sunholo/utils/gcp_project.py,sha256=0ozs6tzI4qEvEeXb8MxLnCdEVoWKxlM6OH05htj7_tc,1325
@@ -115,9 +116,9 @@ sunholo/vertex/extensions_class.py,sha256=E0ix4YqFQG9EglKeTmp2-zwuZUA2crileGahAh
115
116
  sunholo/vertex/init.py,sha256=-w7b9GKsyJnAJpYHYz6_zBUtmeJeLXlEkgOfwoe4DEI,2715
116
117
  sunholo/vertex/memory_tools.py,sha256=WpedE5yDZcQiFOFBSOtwr-tRCnCXW9G6aZ01rFDfsMo,6862
117
118
  sunholo/vertex/safety.py,sha256=S9PgQT1O_BQAkcqauWncRJaydiP8Q_Jzmu9gxYfy1VA,2482
118
- sunholo-0.71.28.dist-info/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
119
- sunholo-0.71.28.dist-info/METADATA,sha256=3W-vWbwG0SH58A2Ozl6T0rUjcli3roM4ExlYp-3519w,6767
120
- sunholo-0.71.28.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
121
- sunholo-0.71.28.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
122
- sunholo-0.71.28.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
123
- sunholo-0.71.28.dist-info/RECORD,,
119
+ sunholo-0.72.0.dist-info/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
120
+ sunholo-0.72.0.dist-info/METADATA,sha256=I_2E5Z5Xq4rQQNgEIp4-WGtlsSXu0PHT2AbuOq8HjIs,6843
121
+ sunholo-0.72.0.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
122
+ sunholo-0.72.0.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
123
+ sunholo-0.72.0.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
124
+ sunholo-0.72.0.dist-info/RECORD,,