nebu 0.1.27__py3-none-any.whl → 0.1.29__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.
nebu/__init__.py CHANGED
@@ -3,10 +3,14 @@
3
3
  # ruff: noqa: F401
4
4
  # ruff: noqa: F403
5
5
 
6
+ from .adapter import *
7
+ from .auth import is_allowed
8
+ from .cache import Cache, OwnedValue
9
+ from .chatx.convert import *
10
+ from .chatx.openai import *
6
11
  from .config import *
7
12
  from .containers.container import Container
8
13
  from .containers.models import *
9
- from .convert import convert_to_unsloth
10
14
  from .data import *
11
15
  from .meta import *
12
16
  from .processors.decorate import *
nebu/adapter.py ADDED
@@ -0,0 +1,11 @@
1
+ import time
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class Adapter(BaseModel):
7
+ created_at: int = Field(default_factory=lambda: int(time.time()))
8
+ name: str
9
+ uri: str
10
+ base_model: str
11
+ owner: str
nebu/auth.py CHANGED
@@ -33,3 +33,18 @@ def get_user_profile(api_key: str) -> V1UserProfile:
33
33
  response.raise_for_status()
34
34
 
35
35
  return V1UserProfile.model_validate(response.json())
36
+
37
+
38
+ def is_allowed(
39
+ resource_owner: str,
40
+ user_id: Optional[str] = None,
41
+ orgs: Optional[Dict[str, Dict[str, str]]] = None,
42
+ ) -> bool:
43
+ if orgs is None:
44
+ orgs = {}
45
+ owners = []
46
+ for org_id, _ in orgs.items():
47
+ owners.append(org_id)
48
+ if user_id:
49
+ owners.append(user_id)
50
+ return resource_owner in owners
nebu/cache.py ADDED
@@ -0,0 +1,90 @@
1
+ import os
2
+ import time
3
+ from typing import Any, Optional, cast
4
+
5
+ import redis
6
+ from pydantic import BaseModel, Field
7
+
8
+
9
+ class OwnedValue(BaseModel):
10
+ created_at: int = Field(default_factory=lambda: int(time.time()))
11
+ value: str
12
+ user_id: Optional[str] = None
13
+ orgs: Optional[Any] = None
14
+ handle: Optional[str] = None
15
+ owner: Optional[str] = None
16
+
17
+
18
+ class Cache:
19
+ """
20
+ A simple cache class that connects to Redis.
21
+ """
22
+
23
+ def __init__(self, host: str = "localhost", port: int = 6379, db: int = 0):
24
+ """
25
+ Initializes the Redis connection.
26
+ Pulls connection details from environment variables REDIS_HOST,
27
+ REDIS_PORT, and REDIS_DB if available, otherwise uses defaults.
28
+ """
29
+ redis_host = os.environ.get("REDIS_HOST", host)
30
+ redis_port = int(os.environ.get("REDIS_PORT", port))
31
+ redis_db = int(os.environ.get("REDIS_DB", db))
32
+ namespace = os.environ.get("NEBU_NAMESPACE")
33
+ if not namespace:
34
+ raise ValueError("NEBU_NAMESPACE environment variable is not set")
35
+
36
+ try:
37
+ # decode_responses=True ensures keys and values are returned as strings
38
+ self.redis_client = redis.StrictRedis(
39
+ host=redis_host, port=redis_port, db=redis_db, decode_responses=True
40
+ )
41
+ # Ping the server to ensure connection is established
42
+ self.redis_client.ping()
43
+ print(
44
+ f"Successfully connected to Redis at {redis_host}:{redis_port}/{redis_db}"
45
+ )
46
+
47
+ self.prefix = f"cache:{namespace}"
48
+ except Exception as e:
49
+ print(f"Error connecting to Redis: {e}")
50
+ self.redis_client = None # Set client to None if connection fails
51
+
52
+ def get(self, key: str) -> str | None:
53
+ """
54
+ Gets the value associated with a key from Redis.
55
+ Returns None if the key does not exist or connection failed.
56
+ """
57
+ if not self.redis_client:
58
+ print("Redis client not connected.")
59
+ return None
60
+ try:
61
+ key = f"{self.prefix}:{key}"
62
+ # Cast the result to str | None as expected
63
+ result = self.redis_client.get(key)
64
+ return cast(str | None, result)
65
+ except Exception as e:
66
+ print(f"Error getting key '{key}' from Redis: {e}")
67
+ return None
68
+
69
+ def set(self, key: str, value: str, expiry_seconds: int | None = None) -> bool:
70
+ """
71
+ Sets a key-value pair in Redis.
72
+ Optionally sets an expiry time for the key in seconds.
73
+ Returns True if successful, False otherwise (e.g., connection failed).
74
+ """
75
+ if not self.redis_client:
76
+ print("Redis client not connected.")
77
+ return False
78
+ try:
79
+ key = f"{self.prefix}:{key}"
80
+ if expiry_seconds:
81
+ # Cast the result to bool
82
+ result = self.redis_client.setex(key, expiry_seconds, value)
83
+ return cast(bool, result)
84
+ else:
85
+ # Cast the result to bool
86
+ result = self.redis_client.set(key, value)
87
+ return cast(bool, result)
88
+ except Exception as e:
89
+ print(f"Error setting key '{key}' in Redis: {e}")
90
+ return False
@@ -1,13 +1,89 @@
1
1
  import base64
2
2
  import binascii
3
3
  import io
4
- from typing import Any, Dict, List
4
+ from io import BytesIO
5
+ from typing import Any, Dict, List, Tuple
5
6
 
6
7
  import requests
7
8
  from PIL import Image, UnidentifiedImageError
8
9
 
9
10
 
10
- def convert_to_unsloth(
11
+ def convert_to_unsloth_inference(
12
+ old_schema: List[Dict[str, Any]],
13
+ ) -> Tuple[List[Dict[str, Any]], List[Image.Image]]:
14
+ """
15
+ Convert from an old OpenAI message format that may look like:
16
+ [
17
+ {
18
+ "role": "user",
19
+ "content": [
20
+ {"type": "text", "text": "some text"},
21
+ {"type": "image_url", "image_url": {"url": "https://..."}},
22
+ ...
23
+ ],
24
+ }
25
+ ]
26
+
27
+ to a new format:
28
+ [
29
+ {
30
+ "role": "user",
31
+ "content": [
32
+ {"type": "image"},
33
+ {"type": "text", "text": "merged user text"}
34
+ ],
35
+ }
36
+ ]
37
+
38
+ Along with the new format, return a list of downloaded PIL Image objects.
39
+ """
40
+
41
+ new_schema = []
42
+ all_images = [] # Will store PIL images as we convert them
43
+
44
+ for message in old_schema:
45
+ role = message.get("role", "user")
46
+
47
+ # Collect all text pieces and all image URLs
48
+ text_chunks = []
49
+ image_urls = []
50
+
51
+ for content_item in message.get("content", []):
52
+ content_type = content_item.get("type")
53
+ if content_type == "text":
54
+ text_chunks.append(content_item.get("text", ""))
55
+ elif content_type == "image_url":
56
+ image_url = content_item.get("image_url", {}).get("url")
57
+ if image_url:
58
+ image_urls.append(image_url)
59
+
60
+ # Merge text chunks into one
61
+ merged_text = " ".join(text_chunks).strip()
62
+
63
+ # Convert each URL into a PIL image
64
+ for url in image_urls:
65
+ # Download the image
66
+ response = requests.get(url)
67
+ response.raise_for_status()
68
+ image_data = BytesIO(response.content)
69
+ pil_img = Image.open(image_data).convert("RGB")
70
+ all_images.append(pil_img)
71
+
72
+ # Construct new message format
73
+ # For simplicity, this example only places one {"type": "image"} placeholder
74
+ # regardless of how many images were found, and merges all text into one block.
75
+ new_content = []
76
+ if image_urls:
77
+ new_content.append({"type": "image"})
78
+ if merged_text:
79
+ new_content.append({"type": "text", "text": merged_text})
80
+
81
+ new_schema.append({"role": role, "content": new_content})
82
+
83
+ return new_schema, all_images
84
+
85
+
86
+ def oai_to_unsloth(
11
87
  messages_input: List[Dict[str, Any]],
12
88
  ) -> Dict[str, List[Dict[str, Any]]]:
13
89
  """