sunholo 0.121.0__py3-none-any.whl → 0.123.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/__init__.py CHANGED
@@ -15,6 +15,8 @@ from . import invoke
15
15
  from . import langfuse
16
16
  from . import llamaindex
17
17
  from . import lookup
18
+ from . import mcp
19
+ from . import ollama
18
20
  from . import pubsub
19
21
  from . import qna
20
22
  from . import senses
@@ -43,6 +45,8 @@ __all__ = ['agents',
43
45
  'langfuse',
44
46
  'llamaindex',
45
47
  'lookup',
48
+ 'mcp',
49
+ 'ollama',
46
50
  'pubsub',
47
51
  'qna',
48
52
  'senses',
@@ -8,6 +8,7 @@ except ImportError:
8
8
  AlloyDBEngine = None
9
9
  pass
10
10
 
11
+ import json
11
12
  from .database import get_vector_size
12
13
  from .uuid import generate_uuid_from_object_id
13
14
  from ..custom_logging import log
@@ -492,4 +493,166 @@ class AlloyDBClient:
492
493
  '''
493
494
  self.execute_sql(sql)
494
495
 
495
- self.grant_table_permissions(vectorstore_id, users)
496
+ self.grant_table_permissions(vectorstore_id, users)
497
+
498
+ async def _execute_sql_async_pg8000(self, sql_statement, values=None):
499
+ """Executes a given SQL statement asynchronously with error handling."""
500
+ sql_ = sqlalchemy.text(sql_statement)
501
+ result = None
502
+ async with self.engine.connect() as conn:
503
+ try:
504
+ log.info(f"Executing SQL statement asynchronously: {sql_}")
505
+ if values:
506
+ result = await conn.execute(sql_, values)
507
+ else:
508
+ result = await conn.execute(sql_)
509
+ except DatabaseError as e:
510
+ if "already exists" in str(e):
511
+ log.warning(f"Error ignored: {str(e)}. Assuming object already exists.")
512
+ else:
513
+ raise
514
+ finally:
515
+ await conn.close()
516
+
517
+ return result
518
+
519
+ async def _execute_sql_async_langchain(self, sql_statement, values=None):
520
+ """Execute SQL asynchronously using langchain engine"""
521
+ if values:
522
+ # Implement parameterized queries for langchain engine
523
+ # This would need to be adjusted based on how langchain engine handles parameters
524
+ log.warning("Parameterized queries may not be fully supported with langchain engine")
525
+ # For now, attempt a basic string substitution (not ideal for production)
526
+ for value in values:
527
+ if isinstance(value, str):
528
+ sql_statement = sql_statement.replace("%s", f"'{value}'", 1)
529
+ else:
530
+ sql_statement = sql_statement.replace("%s", str(value), 1)
531
+
532
+ return await self.engine._afetch(query=sql_statement)
533
+
534
+ async def create_table_from_schema(self, table_name: str, schema_data: dict, users: list = None):
535
+ """
536
+ Creates or ensures a table exists based on the structure of the provided schema data.
537
+
538
+ Args:
539
+ table_name (str): Name of the table to create
540
+ schema_data (dict): Data structure that matches the expected schema
541
+ users (list, optional): List of users to grant permissions to
542
+
543
+ Returns:
544
+ Result of SQL execution
545
+ """
546
+ # Generate column definitions from schema data
547
+ columns = []
548
+ for key, value in schema_data.items():
549
+ if isinstance(value, dict):
550
+ # For nested objects, store as JSONB
551
+ columns.append(f'"{key}" JSONB')
552
+ elif isinstance(value, list):
553
+ # For arrays, store as JSONB
554
+ columns.append(f'"{key}" JSONB')
555
+ elif isinstance(value, int):
556
+ columns.append(f'"{key}" INTEGER')
557
+ elif isinstance(value, float):
558
+ columns.append(f'"{key}" NUMERIC')
559
+ elif isinstance(value, bool):
560
+ columns.append(f'"{key}" BOOLEAN')
561
+ else:
562
+ # Default to TEXT for strings and other types
563
+ columns.append(f'"{key}" TEXT')
564
+
565
+ # Add metadata columns
566
+ columns.extend([
567
+ '"source" TEXT',
568
+ '"extraction_date" TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP',
569
+ '"extraction_backend" TEXT',
570
+ '"extraction_model" TEXT'
571
+ ])
572
+
573
+ # Create SQL statement for table creation
574
+ columns_sql = ", ".join(columns)
575
+ sql = f'''
576
+ CREATE TABLE IF NOT EXISTS "{table_name}" (
577
+ id SERIAL PRIMARY KEY,
578
+ {columns_sql}
579
+ )
580
+ '''
581
+
582
+ # Execute SQL to create table
583
+ result = await self.execute_sql_async(sql)
584
+ log.info(f"Created or ensured table {table_name} exists")
585
+
586
+ # Grant permissions if users are provided
587
+ if users:
588
+ for user in users:
589
+ await self.execute_sql_async(f'GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE "{table_name}" TO "{user}";')
590
+
591
+ return result
592
+
593
+ async def write_data_to_table(self, table_name: str, data: dict, metadata: dict = None):
594
+ """
595
+ Writes data to the specified table.
596
+
597
+ Args:
598
+ table_name (str): Name of the table
599
+ data (dict): Data to write to the table
600
+ metadata (dict, optional): Additional metadata to include
601
+
602
+ Returns:
603
+ Result of SQL execution
604
+ """
605
+ # Create copies to avoid modifying the original data
606
+ insert_data = dict(data)
607
+
608
+ # Add metadata if provided
609
+ if metadata:
610
+ insert_data["source"] = metadata.get("objectId", metadata.get("source", "unknown"))
611
+ insert_data["extraction_backend"] = metadata.get("extraction_backend", "unknown")
612
+ insert_data["extraction_model"] = metadata.get("extraction_model", "unknown")
613
+
614
+ # Prepare column names and placeholders for values
615
+ columns = [f'"{key}"' for key in insert_data.keys()]
616
+ placeholders = []
617
+ values = []
618
+
619
+ # Process values and create properly formatted placeholders
620
+ for key, value in insert_data.items():
621
+ values.append(json.dumps(value) if isinstance(value, (dict, list)) else value)
622
+ placeholders.append("%s")
623
+
624
+ # Create SQL statement for insertion
625
+ columns_str = ", ".join(columns)
626
+ placeholders_str = ", ".join(placeholders)
627
+
628
+ sql = f'''
629
+ INSERT INTO "{table_name}" ({columns_str})
630
+ VALUES ({placeholders_str})
631
+ RETURNING id
632
+ '''
633
+
634
+ # Execute SQL to insert data
635
+ result = await self.execute_sql_async(sql, values)
636
+ log.info(f"Inserted data into table {table_name}")
637
+
638
+ return result
639
+
640
+ async def execute_sql_async(self, sql_statement, values=None):
641
+ """
642
+ Executes a given SQL statement asynchronously with optional parameter values.
643
+
644
+ Args:
645
+ sql_statement (str): The SQL statement to execute
646
+ values (list, optional): Values for parameterized query
647
+
648
+ Returns:
649
+ Result of SQL execution
650
+ """
651
+ log.info(f"Executing async SQL statement: {sql_statement}")
652
+ if self.engine_type == "pg8000":
653
+ result = await self._execute_sql_async_pg8000(sql_statement, values)
654
+ elif self.engine_type == "langchain":
655
+ result = await self._execute_sql_async_langchain(sql_statement, values)
656
+
657
+ return result
658
+
File without changes
@@ -0,0 +1,71 @@
1
+ import os.path
2
+ import argparse
3
+ from typing import List, Optional
4
+ import sys
5
+ from ..custom_logging import log
6
+
7
+ try:
8
+ import PIL.Image
9
+ from ollama import generate
10
+ except ImportError:
11
+ generate = None
12
+
13
+ CHAT_MODEL_NAME = os.getenv("MODEL_NAME_LATEST")
14
+
15
+ def chat_ollama(msg, model_name, the_images=None):
16
+
17
+ if not generate:
18
+ raise ImportError("Import ollama via `pip install ollama`")
19
+
20
+ chat_images = []
21
+ if the_images:
22
+ for the_image in the_images:
23
+ chat_image = PIL.Image.open(the_image)
24
+ chat_images.append(chat_image)
25
+
26
+ log.info(f"Ollama [{model_name}]: Chatting...{msg=}")
27
+ for response in generate(model_name, msg, images=chat_images, stream=True):
28
+ print(response['response'], end='', flush=True)
29
+
30
+ def main():
31
+ parser = argparse.ArgumentParser(description='Chat with Ollama models from the command line')
32
+ parser.add_argument('--model', '-m', type=str, default=CHAT_MODEL_NAME,
33
+ help='Model name to use (defaults to MODEL_NAME_LATEST env var)')
34
+ parser.add_argument('--images', '-i', type=str, nargs='+',
35
+ help='Image file paths to include in the prompt')
36
+ parser.add_argument('--message', '-p', type=str,
37
+ help='Message to send')
38
+
39
+ args = parser.parse_args()
40
+
41
+ if not args.model:
42
+ print("Error: No model specified. Either set MODEL_NAME_LATEST environment variable or use --model flag.")
43
+ sys.exit(1)
44
+
45
+ # If no message provided via args, read from stdin
46
+ if not args.message:
47
+ print(f"Enter your message to {args.model} (Ctrl+D to send):")
48
+ user_input = sys.stdin.read().strip()
49
+ else:
50
+ user_input = args.message
51
+
52
+ if not user_input:
53
+ print("Error: Empty message. Exiting.")
54
+ sys.exit(1)
55
+
56
+ try:
57
+ chat_ollama(user_input, args.model, args.images)
58
+ print() # Add a newline after the response
59
+ except ImportError as e:
60
+ print(f"Error: {e}")
61
+ sys.exit(1)
62
+ except Exception as e:
63
+ print(f"Error: {e}")
64
+ sys.exit(1)
65
+
66
+ if __name__ == "__main__":
67
+ # uv run src/sunholo/ollama/ollama_images.py --model=gemma3:12b - chat and then CTRL+D
68
+ # uv run src/sunholo/ollama/ollama_images.py --model gemma3:12b --message "Tell me about quantum computing"
69
+
70
+
71
+ main()
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: sunholo
3
- Version: 0.121.0
3
+ Version: 0.123.0
4
4
  Summary: Large Language Model DevOps - a package to help deploy LLMs to the Cloud.
5
5
  Author-email: Holosun ApS <multivac@sunholo.com>
6
6
  License: Apache License, Version 2.0
@@ -20,6 +20,8 @@ Description-Content-Type: text/markdown
20
20
  License-File: LICENSE.txt
21
21
  Requires-Dist: aiohttp
22
22
  Requires-Dist: google-auth
23
+ Requires-Dist: ollama>=0.4.7
24
+ Requires-Dist: pillow>=11.0.0
23
25
  Requires-Dist: pydantic
24
26
  Requires-Dist: requests
25
27
  Requires-Dist: ruamel.yaml
@@ -144,6 +146,9 @@ Requires-Dist: langchain-google-genai>=2.0.0; extra == "gcp"
144
146
  Requires-Dist: langchain_google_alloydb_pg>=0.2.2; extra == "gcp"
145
147
  Requires-Dist: langchain-google-vertexai; extra == "gcp"
146
148
  Requires-Dist: pillow; extra == "gcp"
149
+ Provides-Extra: ollama
150
+ Requires-Dist: pillow; extra == "ollama"
151
+ Requires-Dist: ollama>=0.4.7; extra == "ollama"
147
152
  Provides-Extra: openai
148
153
  Requires-Dist: langchain-openai>=0.3.2; extra == "openai"
149
154
  Requires-Dist: tiktoken; extra == "openai"
@@ -175,6 +180,7 @@ Requires-Dist: numpy; extra == "tts"
175
180
  Requires-Dist: sounddevice; extra == "tts"
176
181
  Provides-Extra: video
177
182
  Requires-Dist: opencv-python; extra == "video"
183
+ Dynamic: license-file
178
184
 
179
185
  [![PyPi Version](https://img.shields.io/pypi/v/sunholo.svg)](https://pypi.python.org/pypi/sunholo/)
180
186
 
@@ -1,4 +1,4 @@
1
- sunholo/__init__.py,sha256=Ap2yX2ITBVt_vkloYipUM8OwW14g6aor2NX7LWp0-mI,1133
1
+ sunholo/__init__.py,sha256=InRbX4V0-qdNHo9zYH3GEye7ASLR6LX8-SMvPV4Jsaw,1212
2
2
  sunholo/custom_logging.py,sha256=YfIN1oP3dOEkkYkyRBU8BGS3uJFGwUDsFCl8mIVbwvE,12225
3
3
  sunholo/langchain_types.py,sha256=uZ4zvgej_f7pLqjtu4YP7qMC_eZD5ym_5x4pyvA1Ih4,1834
4
4
  sunholo/agents/__init__.py,sha256=X2I3pPkGeKWjc3d0QgSpkTyqD8J8JtrEWqwrumf1MMc,391
@@ -60,7 +60,7 @@ sunholo/components/retriever.py,sha256=Wmchv3huAM4w7DIS-a5Lp9Hi7M8pE6vZdxgseiT9S
60
60
  sunholo/components/vectorstore.py,sha256=k7GS1Y5c6ZGXSDAJvyCes6dTjhDAi0fjGbVLqpyfzBc,5918
61
61
  sunholo/database/__init__.py,sha256=bpB5Nk21kwqYj-qdVnvNgXjLsbflnH4g-San7OHMqR4,283
62
62
  sunholo/database/alloydb.py,sha256=x1zUMB-EVWbE2Zvp4nAs2Z-tB_kOZmS45H2lwVHdYnk,11678
63
- sunholo/database/alloydb_client.py,sha256=q732tmRdSDutnUk7vRUPUPpi-yU5FK5rQko8co6yke0,19132
63
+ sunholo/database/alloydb_client.py,sha256=uUJN-FYPmGAb9rzGVdg7ds38YhwKBQCXPqUtdOzBQCg,25123
64
64
  sunholo/database/database.py,sha256=VqhZdkXUNdvWn8sUcUV3YNby1JDVf7IykPVXWBtxo9U,7361
65
65
  sunholo/database/lancedb.py,sha256=DyfZntiFKBlVPaFooNN1Z6Pl-LAs4nxWKKuq8GBqN58,715
66
66
  sunholo/database/static_dbs.py,sha256=8cvcMwUK6c32AS2e_WguKXWMkFf5iN3g9WHzsh0C07Q,442
@@ -112,6 +112,8 @@ sunholo/lookup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
112
112
  sunholo/lookup/model_lookup.yaml,sha256=O7o-jP53MLA06C8pI-ILwERShO-xf6z_258wtpZBv6A,739
113
113
  sunholo/mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
114
114
  sunholo/mcp/cli.py,sha256=d24nnVzhZYz4AWgTqmN-qjKG4rPbf8RhdmEOHZkBHy8,10570
115
+ sunholo/ollama/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
116
+ sunholo/ollama/ollama_images.py,sha256=H2cpcNu88R4TwyfL_nnqkQhdvBQ2FPCAy4Ok__0yQmo,2351
115
117
  sunholo/pubsub/__init__.py,sha256=DfTEk4zmCfqn6gFxRrqDO0pOrvXTDqH-medpgYO4PGw,117
116
118
  sunholo/pubsub/process_pubsub.py,sha256=rN2N4WM6PZkMKDrdT8pnEfTvsXACRyJFqIHJQCbuxLs,3088
117
119
  sunholo/pubsub/pubsub_manager.py,sha256=19w_N0LiG-wgVWvgJ13b8BUeN8ZzgSPXAhPmL1HRRSI,6966
@@ -166,9 +168,9 @@ sunholo/vertex/init.py,sha256=1OQwcPBKZYBTDPdyU7IM4X4OmiXLdsNV30C-fee2scQ,2875
166
168
  sunholo/vertex/memory_tools.py,sha256=tBZxqVZ4InTmdBvLlOYwoSEWu4-kGquc-gxDwZCC4FA,7667
167
169
  sunholo/vertex/safety.py,sha256=S9PgQT1O_BQAkcqauWncRJaydiP8Q_Jzmu9gxYfy1VA,2482
168
170
  sunholo/vertex/type_dict_to_json.py,sha256=uTzL4o9tJRao4u-gJOFcACgWGkBOtqACmb6ihvCErL8,4694
169
- sunholo-0.121.0.dist-info/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
170
- sunholo-0.121.0.dist-info/METADATA,sha256=xfdz083WUmJThGmmnsBzJl1kN_l5xw9L8ogD6L0i0qw,9808
171
- sunholo-0.121.0.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
172
- sunholo-0.121.0.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
173
- sunholo-0.121.0.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
174
- sunholo-0.121.0.dist-info/RECORD,,
171
+ sunholo-0.123.0.dist-info/licenses/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
172
+ sunholo-0.123.0.dist-info/METADATA,sha256=ue-wXoQBj7xkEm3nzOg0to1O3counnyi7K3RJfzcv0M,10001
173
+ sunholo-0.123.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
174
+ sunholo-0.123.0.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
175
+ sunholo-0.123.0.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
176
+ sunholo-0.123.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.2)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5