sunholo 0.87.0__py3-none-any.whl → 0.88.2__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/__init__.py CHANGED
@@ -19,6 +19,7 @@ from . import patches
19
19
  from . import pubsub
20
20
  from . import qna
21
21
  from . import streaming
22
+ from . import terraform
22
23
  from . import tools
23
24
  from . import utils
24
25
  from . import vertex
@@ -46,6 +47,7 @@ __all__ = ['agents',
46
47
  'pubsub',
47
48
  'qna',
48
49
  'streaming',
50
+ 'terraform',
49
51
  'tools',
50
52
  'utils',
51
53
  'vertex',
sunholo/cli/cli.py CHANGED
@@ -12,6 +12,7 @@ from .swagger import setup_swagger_subparser
12
12
  from .vertex import setup_vertex_subparser
13
13
  from ..llamaindex import setup_llamaindex_subparser
14
14
  from ..excel import setup_excel_subparser
15
+ from ..terraform import setup_tfvarseditor_subparser
15
16
 
16
17
  from ..utils import ConfigManager
17
18
  from ..utils.version import sunholo_version
@@ -65,13 +66,13 @@ def main(args=None):
65
66
  parser.add_argument('--debug', action='store_true', help='Enable debug output')
66
67
  parser.add_argument('--project', default=default_project, help='GCP project to list Cloud Run services from.')
67
68
  parser.add_argument('--region', default=default_region, help='Region to list Cloud Run services from.')
68
- parser.add_argument('--version', action='store_true', help='Show the version and exit')
69
+ parser.add_argument('-v', '--version', action='store_true', help='Show the version and exit')
69
70
 
70
71
  subparsers = parser.add_subparsers(title='commands',
71
72
  description='Valid commands',
72
73
  help='Commands',
73
74
  dest='command',
74
- required=True)
75
+ required=False)
75
76
 
76
77
  # deploy command
77
78
  setup_deploy_subparser(subparsers)
@@ -95,11 +96,18 @@ def main(args=None):
95
96
  setup_llamaindex_subparser(subparsers)
96
97
  # excel
97
98
  setup_excel_subparser(subparsers)
99
+ # terraform
100
+ setup_tfvarseditor_subparser(subparsers)
98
101
 
99
102
  #TODO: add database setup commands: alloydb and supabase
100
103
 
101
104
  args = parser.parse_args(args)
102
105
 
106
+ # Handle global flags
107
+ if args.version:
108
+ console.print(sunholo_version())
109
+ return
110
+
103
111
  if args.debug:
104
112
  log.setLevel(logging.DEBUG)
105
113
  logging.getLogger().setLevel(logging.DEBUG)
@@ -107,14 +115,9 @@ def main(args=None):
107
115
  log.setLevel(logging.WARNING)
108
116
  logging.getLogger().setLevel(logging.WARNING)
109
117
 
110
- if args.version:
111
- sunholo_version()
112
- return
113
-
118
+ # Handle subcommand
114
119
  if hasattr(args, 'func'):
115
120
  args.func(args)
116
121
  else:
117
122
  parser.print_help()
118
123
 
119
- if __name__ == "__main__":
120
- main()
sunholo/cli/cli_init.py CHANGED
@@ -27,7 +27,8 @@ sunholo init my_genai_project
27
27
  This will create a new directory named `my_genai_project` with the template files, allowing users to start building their GenAI application.
28
28
  """
29
29
  project_name = args.project_name
30
- project_dir = os.path.join(os.getcwd(), project_name)
30
+ current_dir = os.getcwd() # This captures the current directory where the command is run
31
+ project_dir = os.path.join(current_dir, project_name)
31
32
 
32
33
  print(f"Initializing project: {project_name} in directory: {project_dir}")
33
34
 
@@ -48,6 +49,59 @@ This will create a new directory named `my_genai_project` with the template file
48
49
  elif os.path.isdir(src_path):
49
50
  shutil.copytree(src_path, dest_path)
50
51
 
52
+ # Determine the location of the generated.tfvars file
53
+ terraform_dir = args.terraform_dir or os.getenv('MULTIVAC_TERRAFORM_DIR')
54
+ if terraform_dir is None:
55
+ raise ValueError("Must specify a terraform_dir or use the MULTIVAC_TERRAFORM_DIR environment variable")
56
+
57
+ tfvars_file = os.path.join(terraform_dir, 'generated.tfvars')
58
+
59
+ # Get the service account, either from the CLI argument or default
60
+ service_account = args.service_account or "sa-llmops" # Default service account
61
+
62
+ # Determine the relative path for the cloud build included directories
63
+ def get_relative_application_path(full_path: str, base_dir: str) -> str:
64
+ application_base_index = full_path.find("application/")
65
+ if application_base_index != -1:
66
+ return full_path[application_base_index:]
67
+ return os.path.relpath(full_path, base_dir)
68
+
69
+ # Paths to be included in the cloud build (based on the current working directory)
70
+ # We want paths to start from 'application/system_services/{project_name}'
71
+ relative_base = os.path.relpath(current_dir, os.path.join(current_dir, "..", ".."))
72
+ included_path = os.path.join(relative_base, project_name, "**")
73
+ cloud_build_path = os.path.join(relative_base, project_name, "cloudbuild.yaml")
74
+
75
+ # Define the cloud_run configuration for 'discord-server' with the correct project_dir path
76
+ cloud_run_config = {
77
+ project_name: {
78
+ "cpu": "1",
79
+ "memory": "2Gi",
80
+ "max_instance_count": 3,
81
+ "timeout_seconds": 1500,
82
+ "port": 8080,
83
+ "service_account": service_account,
84
+ "invokers": ["allUsers"],
85
+ "cloud_build": {
86
+ "included": [included_path],
87
+ "path": cloud_build_path,
88
+ "substitutions": {},
89
+ "repo_name": "",
90
+ "repo_owner": ""
91
+ }
92
+ }
93
+ }
94
+
95
+
96
+ # Initialize the TerraformVarsEditor and update the .tfvars file
97
+ try:
98
+ from ..terraform import TerraformVarsEditor
99
+ editor = TerraformVarsEditor(tfvars_file, terraform_dir)
100
+ editor.update_from_dict(cloud_run_config, 'cloud_run')
101
+ print(f"{tfvars_file} file initialized and updated successfully.")
102
+ except ImportError as e:
103
+ print(f"Error initializing TerraformVarsEditor: {e}")
104
+
51
105
  print(f"Project {project_name} initialized successfully.")
52
106
  print(f"Navigate to {project_dir} and customize the configuration files in the 'config' directory.")
53
107
 
@@ -57,4 +111,6 @@ def setup_init_subparser(subparsers):
57
111
  """
58
112
  init_parser = subparsers.add_parser('init', help='Initializes a new Multivac project.')
59
113
  init_parser.add_argument('project_name', help='The name of the new project.')
114
+ init_parser.add_argument('--terraform-dir', help='The directory where Terraform files will be generated.')
115
+ init_parser.add_argument('--service-account', help='The service account to use for Cloud Run. Defaults to "sa-llmops"')
60
116
  init_parser.set_defaults(func=init_project)
@@ -0,0 +1 @@
1
+ from .tfvars_editor import setup_tfvarseditor_subparser, TerraformVarsEditor
@@ -0,0 +1,339 @@
1
+ try:
2
+ import hcl2
3
+ except ImportError:
4
+ hcl2 = None
5
+
6
+ import json
7
+ import subprocess
8
+ import os
9
+ import io
10
+ from typing import Dict, Any
11
+ from ..custom_logging import log
12
+
13
+ try:
14
+ from ..cli.sun_rich import console
15
+ except ImportError:
16
+ console = None
17
+
18
+ class TerraformVarsEditor:
19
+ """
20
+ A class to manage and safely edit Terraform .tfvars files.
21
+
22
+ This class allows you to update specific keys in a .tfvars file with new data
23
+ and ensures that the changes only take effect if Terraform validation passes.
24
+
25
+ Attributes:
26
+ ----------
27
+ tfvars_file : str
28
+ The path to the .tfvars file to be edited.
29
+ terraform_dir : str
30
+ The directory where Terraform commands will be executed (default is current directory).
31
+ tfvars_data : dict
32
+ The content of the .tfvars file loaded into a dictionary.
33
+
34
+ Methods:
35
+ -------
36
+ _load_tfvars() -> Dict[str, Any]
37
+ Loads the .tfvars file into a dictionary.
38
+ _save_tfvars() -> None
39
+ Saves the current state of the dictionary back to the .tfvars file.
40
+ _backup_tfvars() -> str
41
+ Creates a backup of the current .tfvars file.
42
+ _restore_tfvars(backup_file: str) -> None
43
+ Restores the .tfvars file from the backup.
44
+ update_or_add_instance(main_key: str, instance_name: str, instance_data: Dict[str, Any]) -> None
45
+ Adds or updates an instance under a specified top-level key in the .tfvars file.
46
+ validate_terraform() -> bool
47
+ Runs `terraform validate` in the specified directory.
48
+ update_from_json(json_file: str, main_key: str) -> None
49
+ Updates the .tfvars file based on the content of a JSON file and validates the changes.
50
+ """
51
+
52
+ def __init__(self, tfvars_file: str, terraform_dir: str = '.') -> None:
53
+ """
54
+ Initializes the TerraformVarsEditor with the given .tfvars file and Terraform directory.
55
+
56
+ Parameters:
57
+ ----------
58
+ tfvars_file : str
59
+ The path to the .tfvars file to be edited.
60
+ terraform_dir : str
61
+ The directory where Terraform commands will be executed (default is current directory). Will use MULTIVAC_TERRAFORM_DIR env var if present.
62
+
63
+ Example:
64
+ -------
65
+ editor = TerraformVarsEditor('example.tfvars', '/path/to/terraform/config')
66
+ """
67
+ if hcl2 is None:
68
+ raise ImportError('hcl2 is required for parsing terraform files, install via `pip install sunholo[iac]`')
69
+
70
+ # Check for the MULTIVAC_TERRAFORM_DIR environment variable
71
+ if terraform_dir == '.' and 'MULTIVAC_TERRAFORM_DIR' in os.environ:
72
+ terraform_dir = os.environ['MULTIVAC_TERRAFORM_DIR']
73
+
74
+ log.info(f'MULTIVAC_TERRAFORM_DIR environment variable is set to {terraform_dir}')
75
+
76
+ self.tfvars_file = tfvars_file
77
+ self.terraform_dir = terraform_dir
78
+ self.tfvars_data = self._load_tfvars()
79
+
80
+ def _load_tfvars(self) -> Dict[str, Any]:
81
+ """
82
+ Loads the .tfvars file into a dictionary.
83
+
84
+ Returns:
85
+ -------
86
+ dict
87
+ The content of the .tfvars file.
88
+
89
+ Example:
90
+ -------
91
+ data = self._load_tfvars()
92
+ """
93
+ with open(self.tfvars_file, 'r') as file:
94
+ return hcl2.load(file)
95
+
96
+ def _save_tfvars(self) -> None:
97
+ """
98
+ Saves the current state of the dictionary back to the .tfvars file.
99
+
100
+ Example:
101
+ -------
102
+ self._save_tfvars()
103
+ """
104
+ with open(self.tfvars_file, 'w') as file:
105
+ for key, value in self.tfvars_data.items():
106
+ file.write(f'{key} = {json.dumps(value, indent=2)}\n')
107
+
108
+ def _backup_tfvars(self) -> str:
109
+ """
110
+ Creates a backup of the current .tfvars file.
111
+
112
+ Returns:
113
+ -------
114
+ str
115
+ The path to the backup file.
116
+
117
+ Example:
118
+ -------
119
+ backup_file = self._backup_tfvars()
120
+ """
121
+ backup_file = f"{self.tfvars_file}.bak"
122
+ os.rename(self.tfvars_file, backup_file)
123
+ return backup_file
124
+
125
+ def _restore_tfvars(self, backup_file: str) -> None:
126
+ """
127
+ Restores the .tfvars file from the backup.
128
+
129
+ Parameters:
130
+ ----------
131
+ backup_file : str
132
+ The path to the backup file to restore from.
133
+
134
+ Example:
135
+ -------
136
+ self._restore_tfvars('example.tfvars.bak')
137
+ """
138
+ os.rename(backup_file, self.tfvars_file)
139
+
140
+ def update_or_add_instance(self, main_key: str, instance_name: str, instance_data: Dict[str, Any]) -> None:
141
+ """
142
+ Adds or updates an instance under a specified top-level key in the .tfvars file.
143
+
144
+ Parameters:
145
+ ----------
146
+ main_key : str
147
+ The top-level key in the .tfvars file (e.g., "cloud_run").
148
+ instance_name : str
149
+ The name of the instance to add or update.
150
+ instance_data : dict
151
+ The dictionary containing the instance data.
152
+
153
+ Example:
154
+
155
+ ```python
156
+ editor.update_or_add_instance('cloud_run', 'new_service', (your dict))
157
+ ```
158
+ """
159
+ if main_key not in self.tfvars_data:
160
+ self.tfvars_data[main_key] = {}
161
+
162
+ self.tfvars_data[main_key][instance_name] = instance_data
163
+
164
+ def validate_terraform(self) -> bool:
165
+ """
166
+ Runs `terraform init` followed by `terraform validate` in the specified directory.
167
+
168
+ Returns:
169
+ -------
170
+ bool
171
+ True if validation passes, False otherwise.
172
+
173
+ Example:
174
+ -------
175
+ ```python
176
+ if self.validate_terraform():
177
+ print("Validation passed.")
178
+ ```
179
+ """
180
+ # Step 1: Run `terraform init` to ensure the directory is initialized
181
+ init_process = subprocess.run(['terraform', 'init'], cwd=self.terraform_dir, capture_output=True, text=True)
182
+
183
+ if init_process.returncode != 0:
184
+ log.error("Terraform initialization failed.")
185
+ print(init_process.stdout)
186
+ print(init_process.stderr)
187
+ return False
188
+
189
+ log.info("Terraform initialized successfully.")
190
+
191
+ # Step 2: Run `terraform validate`
192
+ validate_process = subprocess.run(['terraform', 'validate'], cwd=self.terraform_dir, capture_output=True, text=True)
193
+
194
+ if validate_process.returncode == 0:
195
+ log.info("Terraform validation passed.")
196
+ return True
197
+ else:
198
+ log.error("Terraform validation failed.")
199
+ print(validate_process.stdout)
200
+ print(validate_process.stderr)
201
+ return False
202
+
203
+ def update_from_json(self, json_file: str, main_key: str) -> None:
204
+ """
205
+ Updates the .tfvars file based on the content of a JSON file and validates the changes.
206
+
207
+ Parameters:
208
+ ----------
209
+ json_file : str
210
+ The path to the JSON file with the new instance data.
211
+ main_key : str
212
+ The top-level key in the .tfvars file (e.g., "cloud_run").
213
+
214
+ Example:
215
+ -------
216
+ editor.update_from_json('update.json', 'cloud_run')
217
+ """
218
+ with open(json_file, 'r') as file:
219
+ data = json.load(file)
220
+
221
+ # Update the tfvars data in memory
222
+ for instance_name, instance_data in data.get(main_key, {}).items():
223
+ self.update_or_add_instance(main_key, instance_name, instance_data)
224
+
225
+ # Backup the original .tfvars file
226
+ backup_file = self._backup_tfvars()
227
+
228
+ # Temporarily save the updated data to the original file location
229
+ self._save_tfvars()
230
+
231
+ # Attempt to validate the changes with Terraform
232
+ if not self.validate_terraform():
233
+ # If validation fails, restore the original file from the backup
234
+ self._restore_tfvars(backup_file)
235
+ log.error(f"Changes aborted, original {self.tfvars_file} restored.")
236
+ else:
237
+ log.info(f"Terraform validation passed, changes saved to {self.tfvars_file}.")
238
+ os.remove(backup_file) # Remove the backup if validation passes
239
+
240
+ def update_from_dict(self, data: Dict[str, Any], main_key: str) -> None:
241
+ """
242
+ Updates the .tfvars file based on the content of a Python dictionary and validates the changes.
243
+
244
+ Parameters:
245
+ ----------
246
+ data : dict
247
+ The dictionary with the new instance data.
248
+ main_key : str
249
+ The top-level key under which the instance is added (e.g., "cloud_run").
250
+
251
+ Example:
252
+ -------
253
+ editor.update_from_dict(data, 'cloud_run')
254
+ """
255
+ # Create an in-memory file-like object from the dictionary by converting it to JSON
256
+ json_data = json.dumps({main_key: data})
257
+ json_file = io.StringIO(json_data)
258
+
259
+ # Load the JSON data from the StringIO object
260
+ parsed_data = json.load(json_file)
261
+
262
+ # Update the tfvars data in memory
263
+ for instance_name, instance_data in parsed_data.get(main_key, {}).items():
264
+ self.update_or_add_instance(main_key, instance_name, instance_data)
265
+
266
+ # Now that the data is updated in memory, proceed to validate and write it back to the file
267
+ # Backup the original .tfvars file
268
+ backup_file = self._backup_tfvars()
269
+
270
+ # Temporarily save the updated data to the original file location
271
+ self._save_tfvars()
272
+
273
+ # Attempt to validate the changes with Terraform
274
+ if not self.validate_terraform():
275
+ # If validation fails, restore the original file from the backup
276
+ self._restore_tfvars(backup_file)
277
+ console.print(f"Changes aborted, original {self.tfvars_file} restored.")
278
+ else:
279
+ console.print(f"Terraform validation passed, changes saved to {self.tfvars_file}.")
280
+ os.remove(backup_file) # Remove the backup if validation passes
281
+
282
+ def tfvars_command(args):
283
+ """
284
+ Executes the tfvars command based on parsed arguments.
285
+
286
+ Args:
287
+ args: The parsed command-line arguments.
288
+ """
289
+
290
+ if console is None:
291
+ raise ImportError("Need cli tools to use `sunholo tfvars` - install via `pip install sunholo[cli]`")
292
+
293
+ # Load JSON data from the specified file
294
+ try:
295
+ with open(args.json_file, 'r') as f:
296
+ instance_data = json.load(f)
297
+ except FileNotFoundError:
298
+ console.print(f"Error: The JSON file '{args.json_file}' was not found.")
299
+ return
300
+ except json.JSONDecodeError as e:
301
+ console.print(f"Error parsing JSON data: {e}")
302
+ return
303
+
304
+ # Create an instance of TerraformVarsEditor
305
+ editor = TerraformVarsEditor(args.tfvars_file, args.terraform_dir)
306
+
307
+ # Add or update the instance
308
+ editor.update_or_add_instance(args.main_key, args.instance_name, instance_data)
309
+
310
+ # Validate the Terraform configuration
311
+ if editor.validate_terraform():
312
+ console.print(f"Successfully updated '{args.instance_name}' under '{args.main_key}' in '{args.tfvars_file}'.")
313
+ else:
314
+ console.print(f"[bold red]Failed to update '{args.instance_name}'. The changes have been rolled back.[/bold red]")
315
+
316
+ def setup_tfvarseditor_subparser(subparsers):
317
+ """
318
+ Sets up an argparse subparser for the 'tfvars' command.
319
+
320
+ Args:
321
+ subparsers: The subparsers object from argparse.ArgumentParser().
322
+ """
323
+ # TFVars subparser setup
324
+ tfvars_parser = subparsers.add_parser('tfvars', help='Manage Terraform .tfvars files')
325
+ tfvars_subparsers = tfvars_parser.add_subparsers(dest='action', help='TFVars subcommands')
326
+
327
+ # TFVars add command
328
+ add_parser = tfvars_subparsers.add_parser('add', help='Add or update an instance in a .tfvars file')
329
+ add_parser.add_argument('tfvars_file', help='Path to the .tfvars file')
330
+ add_parser.add_argument('main_key', help='The main key under which the instance is added (e.g., "cloud_run")')
331
+ add_parser.add_argument('instance_name', help='The name of the instance to add or update')
332
+ add_parser.add_argument('--json-file', help='Path to a JSON file with the instance data', required=True)
333
+ add_parser.add_argument('--terraform-dir', default='.', help='The directory where Terraform is initialized')
334
+
335
+ tfvars_parser.set_defaults(func=tfvars_command)
336
+
337
+ # If no subcommand is provided, print the help message
338
+ tfvars_parser.set_defaults(func=lambda args: tfvars_parser.print_help() if args.action is None else tfvars_command)
339
+
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sunholo
3
- Version: 0.87.0
3
+ Version: 0.88.2
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.87.0.tar.gz
6
+ Download-URL: https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.88.2.tar.gz
7
7
  Author: Holosun ApS
8
8
  Author-email: multivac@sunholo.com
9
9
  License: Apache License, Version 2.0
@@ -64,6 +64,7 @@ Requires-Dist: playwright ; extra == 'all'
64
64
  Requires-Dist: psutil ; extra == 'all'
65
65
  Requires-Dist: psycopg2-binary ; extra == 'all'
66
66
  Requires-Dist: pypdf ; extra == 'all'
67
+ Requires-Dist: python-hcl2 ; extra == 'all'
67
68
  Requires-Dist: python-socketio ; extra == 'all'
68
69
  Requires-Dist: pytesseract ; extra == 'all'
69
70
  Requires-Dist: rich ; extra == 'all'
@@ -121,6 +122,8 @@ Requires-Dist: httpx ; extra == 'http'
121
122
  Requires-Dist: langfuse ; extra == 'http'
122
123
  Requires-Dist: python-socketio ; extra == 'http'
123
124
  Requires-Dist: requests ; extra == 'http'
125
+ Provides-Extra: iac
126
+ Requires-Dist: python-hcl2 ; extra == 'iac'
124
127
  Provides-Extra: openai
125
128
  Requires-Dist: langchain-openai ; extra == 'openai'
126
129
  Requires-Dist: tiktoken ; extra == 'openai'
@@ -1,4 +1,4 @@
1
- sunholo/__init__.py,sha256=z2utHQ5CvmbkiM1WpB2XciqQ98aI-SFRBy0OZk_GkwY,1088
1
+ sunholo/__init__.py,sha256=lLuVyilzmDbTaiAptR8SZzpbUNsgwHFsp4Ejbr5EApI,1136
2
2
  sunholo/custom_logging.py,sha256=YfIN1oP3dOEkkYkyRBU8BGS3uJFGwUDsFCl8mIVbwvE,12225
3
3
  sunholo/agents/__init__.py,sha256=X2I3pPkGeKWjc3d0QgSpkTyqD8J8JtrEWqwrumf1MMc,391
4
4
  sunholo/agents/chat_history.py,sha256=Gph_CdlP2otYnNdR1q1Umyyyvcad2F6K3LxU5yBQ9l0,5387
@@ -43,8 +43,8 @@ sunholo/chunker/pubsub.py,sha256=48bhuAcszN7LGe3-ksPSLHHhq0uKxiXOrizck5qpcP0,101
43
43
  sunholo/chunker/splitter.py,sha256=QLAEsJOpEYFZr9-UGZUuAlNVyjfCWb8jvzCHg0rVShE,6751
44
44
  sunholo/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
45
  sunholo/cli/chat_vac.py,sha256=jTmCt62U4uqJjeGp_XfIbxnafAAOhQAJMi8TxSkeKlM,23164
46
- sunholo/cli/cli.py,sha256=cWnsXG07IE6fLT1j68ua0GbG9CuoaW8oHRsfRKFt-rE,4227
47
- sunholo/cli/cli_init.py,sha256=JMZ9AX2cPDZ-_mv3adiv2ToFVNyRPtjk9Biszl1kiR0,2358
46
+ sunholo/cli/cli.py,sha256=q-ATmnWnc_PPo2q31xhqpp-m3GtOSBoqh0gvMeSo4XU,4374
47
+ sunholo/cli/cli_init.py,sha256=jifczqRd4ATrfUPl-LYCpuhURZiEwqAy1MaAgpSdxQ4,5025
48
48
  sunholo/cli/configs.py,sha256=QUM9DvKOdZmEQRM5uI3Nh887T0YDiSMr7O240zTLqws,4546
49
49
  sunholo/cli/deploy.py,sha256=zxdwUsRTRMC8U5vyRv0JiKBLFn84Ug_Tc88-_h9hJSs,1609
50
50
  sunholo/cli/embedder.py,sha256=v-FKiSPHaQzB6ctClclYueIf3bf3CqYtC1oRgPfT4dY,5566
@@ -119,6 +119,8 @@ sunholo/streaming/stream_lookup.py,sha256=hYg1DbdSE_QNJ8ZB-ynXJlWgvFjrGvwoUsGJu_
119
119
  sunholo/streaming/streaming.py,sha256=5dRXo1wHYE0E91gll-939RShYTYxQMeVwAVCqFR13h0,16343
120
120
  sunholo/summarise/__init__.py,sha256=MZk3dblUMODcPb1crq4v-Z508NrFIpkSWNf9FIO8BcU,38
121
121
  sunholo/summarise/summarise.py,sha256=95A-6PXFGanjona8DvZPnnIHLbzZ2ip5hO0wOAJQhfw,3791
122
+ sunholo/terraform/__init__.py,sha256=yixxEltc3n9UpZaVi05GlgS-YRq_DVGjUc37I9ajeP4,76
123
+ sunholo/terraform/tfvars_editor.py,sha256=iBKyDajnZsDnLWuqGZv9SJKWMfK0J809-I6X0_EnUHU,12395
122
124
  sunholo/tools/__init__.py,sha256=5NuYpwwTX81qGUWvgwfItoSLXteNnp7KjgD7IPZUFjI,53
123
125
  sunholo/tools/web_browser.py,sha256=8Gdf02F4zCOeSnijnfaL6jzk4oaSI0cj48o-esoWzwE,29086
124
126
  sunholo/utils/__init__.py,sha256=Hv02T5L2zYWvCso5hzzwm8FQogwBq0OgtUbN_7Quzqc,89
@@ -141,9 +143,9 @@ sunholo/vertex/init.py,sha256=1OQwcPBKZYBTDPdyU7IM4X4OmiXLdsNV30C-fee2scQ,2875
141
143
  sunholo/vertex/memory_tools.py,sha256=q_phxgGX2TG2j2MXNULF2xGzQnQPENwjPN9nZ_A9Gh0,7526
142
144
  sunholo/vertex/safety.py,sha256=S9PgQT1O_BQAkcqauWncRJaydiP8Q_Jzmu9gxYfy1VA,2482
143
145
  sunholo/vertex/type_dict_to_json.py,sha256=uTzL4o9tJRao4u-gJOFcACgWGkBOtqACmb6ihvCErL8,4694
144
- sunholo-0.87.0.dist-info/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
145
- sunholo-0.87.0.dist-info/METADATA,sha256=Sy38TeVoPDo6SPuonnZiDu9__xGrBb-rVPaHpUvToVc,7598
146
- sunholo-0.87.0.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
147
- sunholo-0.87.0.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
148
- sunholo-0.87.0.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
149
- sunholo-0.87.0.dist-info/RECORD,,
146
+ sunholo-0.88.2.dist-info/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
147
+ sunholo-0.88.2.dist-info/METADATA,sha256=HUlu0wOukUKSs4pQW1k2F3xwu8x6nQD8WRO4dukQGKk,7706
148
+ sunholo-0.88.2.dist-info/WHEEL,sha256=UvcQYKBHoFqaQd6LKyqHw9fxEolWLQnlzP0h_LgJAfI,91
149
+ sunholo-0.88.2.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
150
+ sunholo-0.88.2.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
151
+ sunholo-0.88.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (73.0.1)
2
+ Generator: setuptools (74.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5