optimizely-opal.opal-tools-sdk 0.1.2.dev0__py3-none-any.whl → 0.1.4.dev0__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.
- opal_tools_sdk/__init__.py +2 -1
- opal_tools_sdk/decorators.py +9 -8
- opal_tools_sdk/logging.py +37 -0
- opal_tools_sdk/models.py +6 -6
- opal_tools_sdk/service.py +10 -10
- {optimizely_opal_opal_tools_sdk-0.1.2.dev0.dist-info → optimizely_opal_opal_tools_sdk-0.1.4.dev0.dist-info}/METADATA +1 -1
- optimizely_opal_opal_tools_sdk-0.1.4.dev0.dist-info/RECORD +11 -0
- optimizely_opal_opal_tools_sdk-0.1.2.dev0.dist-info/RECORD +0 -10
- {optimizely_opal_opal_tools_sdk-0.1.2.dev0.dist-info → optimizely_opal_opal_tools_sdk-0.1.4.dev0.dist-info}/WHEEL +0 -0
- {optimizely_opal_opal_tools_sdk-0.1.2.dev0.dist-info → optimizely_opal_opal_tools_sdk-0.1.4.dev0.dist-info}/top_level.txt +0 -0
opal_tools_sdk/__init__.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from .service import ToolsService
|
|
2
2
|
from .decorators import tool
|
|
3
3
|
from .auth import requires_auth
|
|
4
|
+
from .logging import register_logger_factory
|
|
4
5
|
|
|
5
6
|
__version__ = "0.1.0"
|
|
6
|
-
__all__ = ["ToolsService", "tool", "requires_auth"]
|
|
7
|
+
__all__ = ["ToolsService", "tool", "requires_auth", "register_logger_factory"]
|
opal_tools_sdk/decorators.py
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
import re
|
|
3
3
|
import logging
|
|
4
|
-
from typing import Callable, Any, List, Dict, Type, get_type_hints, Optional, Union
|
|
4
|
+
from typing import Callable, Any, List, Dict, Type, get_origin, get_type_hints, Optional, Union
|
|
5
5
|
from fastapi import APIRouter, Depends, Header, HTTPException
|
|
6
6
|
from pydantic import BaseModel
|
|
7
7
|
|
|
8
8
|
from .models import Parameter, ParameterType, AuthRequirement
|
|
9
|
+
from .logging import get_logger
|
|
9
10
|
|
|
10
|
-
logger =
|
|
11
|
+
logger = get_logger(__name__)
|
|
11
12
|
|
|
12
13
|
def tool(name: str, description: str, auth_requirements: Optional[List[Dict[str, Any]]] = None):
|
|
13
14
|
"""Decorator to register a function as an Opal tool.
|
|
@@ -69,9 +70,9 @@ def tool(name: str, description: str, auth_requirements: Optional[List[Dict[str,
|
|
|
69
70
|
param_type = ParameterType.number
|
|
70
71
|
elif field_type == bool:
|
|
71
72
|
param_type = ParameterType.boolean
|
|
72
|
-
elif field_type
|
|
73
|
+
elif get_origin(field_type) is list:
|
|
73
74
|
param_type = ParameterType.list
|
|
74
|
-
elif field_type
|
|
75
|
+
elif get_origin(field_type) is dict:
|
|
75
76
|
param_type = ParameterType.dictionary
|
|
76
77
|
|
|
77
78
|
# Determine if required
|
|
@@ -95,9 +96,9 @@ def tool(name: str, description: str, auth_requirements: Optional[List[Dict[str,
|
|
|
95
96
|
required=required
|
|
96
97
|
))
|
|
97
98
|
|
|
98
|
-
|
|
99
|
+
logger.info(f"Registered parameter: {field_name} of type {param_type.value}, required: {required}")
|
|
99
100
|
else:
|
|
100
|
-
|
|
101
|
+
logger.warning(f"Warning: No parameter model found for {name}")
|
|
101
102
|
|
|
102
103
|
endpoint = f"/tools/{name}"
|
|
103
104
|
|
|
@@ -112,10 +113,10 @@ def tool(name: str, description: str, auth_requirements: Optional[List[Dict[str,
|
|
|
112
113
|
required=auth_req.get("required", True)
|
|
113
114
|
))
|
|
114
115
|
|
|
115
|
-
|
|
116
|
+
logger.info(f"Registering tool {name} with endpoint {endpoint}")
|
|
116
117
|
|
|
117
118
|
if not _registry.services:
|
|
118
|
-
|
|
119
|
+
logger.warning("No services registered in registry! Make sure to create ToolsService before decorating functions.")
|
|
119
120
|
|
|
120
121
|
for service in _registry.services:
|
|
121
122
|
service.register_tool(
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
from typing import Optional, Callable
|
|
4
|
+
|
|
5
|
+
# Type alias for a logger factory function
|
|
6
|
+
type LoggerFactory = Callable[[Optional[str]], logging.Logger]
|
|
7
|
+
|
|
8
|
+
# Internal variable to hold a custom logger factory
|
|
9
|
+
_custom_logger_factory: Optional[LoggerFactory] = None
|
|
10
|
+
|
|
11
|
+
def register_logger_factory(factory: LoggerFactory):
|
|
12
|
+
"""
|
|
13
|
+
Register a custom logger factory function. This function should accept a name (str or None)
|
|
14
|
+
and return a logger instance (e.g., structlog or standard logger).
|
|
15
|
+
"""
|
|
16
|
+
global _custom_logger_factory
|
|
17
|
+
_custom_logger_factory = factory
|
|
18
|
+
|
|
19
|
+
def get_logger(name: str = None) -> logging.Logger:
|
|
20
|
+
"""
|
|
21
|
+
Returns a logger configured to output to the console, or uses a registered custom logger factory.
|
|
22
|
+
If no name is provided, uses the root logger.
|
|
23
|
+
"""
|
|
24
|
+
if _custom_logger_factory is not None:
|
|
25
|
+
return _custom_logger_factory(name)
|
|
26
|
+
logger = logging.getLogger(name)
|
|
27
|
+
if not logger.handlers:
|
|
28
|
+
handler = logging.StreamHandler(sys.stdout)
|
|
29
|
+
formatter = logging.Formatter(
|
|
30
|
+
fmt='%(asctime)s %(levelname)s [%(name)s] %(message)s',
|
|
31
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
|
32
|
+
)
|
|
33
|
+
handler.setFormatter(formatter)
|
|
34
|
+
logger.addHandler(handler)
|
|
35
|
+
logger.setLevel(logging.INFO)
|
|
36
|
+
logger.propagate = False
|
|
37
|
+
return logger
|
opal_tools_sdk/models.py
CHANGED
|
@@ -8,7 +8,7 @@ class ParameterType(str, Enum):
|
|
|
8
8
|
integer = "integer"
|
|
9
9
|
number = "number"
|
|
10
10
|
boolean = "boolean"
|
|
11
|
-
list = "
|
|
11
|
+
list = "array" # Changed to match main service expectation
|
|
12
12
|
dictionary = "object" # Standard JSON schema type
|
|
13
13
|
|
|
14
14
|
@dataclass
|
|
@@ -18,7 +18,7 @@ class Parameter:
|
|
|
18
18
|
param_type: ParameterType
|
|
19
19
|
description: str
|
|
20
20
|
required: bool
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
def to_dict(self) -> Dict[str, Any]:
|
|
23
23
|
"""Convert to dictionary for the discovery endpoint."""
|
|
24
24
|
return {
|
|
@@ -34,7 +34,7 @@ class AuthRequirement:
|
|
|
34
34
|
provider: str # e.g., "google", "microsoft"
|
|
35
35
|
scope_bundle: str # e.g., "calendar", "drive"
|
|
36
36
|
required: bool = True
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
def to_dict(self) -> Dict[str, Any]:
|
|
39
39
|
"""Convert to dictionary for the discovery endpoint."""
|
|
40
40
|
return {
|
|
@@ -52,7 +52,7 @@ class Function:
|
|
|
52
52
|
endpoint: str
|
|
53
53
|
auth_requirements: Optional[List[AuthRequirement]] = None
|
|
54
54
|
http_method: str = "POST"
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
def to_dict(self) -> Dict[str, Any]:
|
|
57
57
|
"""Convert to dictionary for the discovery endpoint."""
|
|
58
58
|
result = {
|
|
@@ -62,8 +62,8 @@ class Function:
|
|
|
62
62
|
"endpoint": self.endpoint,
|
|
63
63
|
"http_method": self.http_method
|
|
64
64
|
}
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
if self.auth_requirements:
|
|
67
67
|
result["auth_requirements"] = [auth.to_dict() for auth in self.auth_requirements]
|
|
68
|
-
|
|
68
|
+
|
|
69
69
|
return result
|
opal_tools_sdk/service.py
CHANGED
|
@@ -8,7 +8,7 @@ from pydantic import BaseModel, create_model
|
|
|
8
8
|
from .models import Function, Parameter, ParameterType, AuthRequirement
|
|
9
9
|
from . import _registry
|
|
10
10
|
|
|
11
|
-
logger = logging.getLogger(
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
12
|
|
|
13
13
|
class ToolsService:
|
|
14
14
|
"""Main class for managing Opal tools."""
|
|
@@ -91,7 +91,7 @@ class ToolsService:
|
|
|
91
91
|
endpoint: API endpoint for the tool
|
|
92
92
|
auth_requirements: List of authentication requirements (optional)
|
|
93
93
|
"""
|
|
94
|
-
|
|
94
|
+
logger.info(f"Registering tool: {name} with endpoint: {endpoint}")
|
|
95
95
|
|
|
96
96
|
# Extract auth requirements from handler if decorated with @requires_auth
|
|
97
97
|
handler_auth_requirements = self._extract_auth_requirements(handler)
|
|
@@ -116,7 +116,7 @@ class ToolsService:
|
|
|
116
116
|
try:
|
|
117
117
|
# Parse JSON body
|
|
118
118
|
body = await request.json()
|
|
119
|
-
|
|
119
|
+
logger.info(f"Received request for {endpoint} with body: {body}")
|
|
120
120
|
|
|
121
121
|
# Parameters should be in the "parameters" key according to the spec
|
|
122
122
|
# This matches how the tools-mgmt-service calls tools
|
|
@@ -124,20 +124,20 @@ class ToolsService:
|
|
|
124
124
|
params = body["parameters"]
|
|
125
125
|
else:
|
|
126
126
|
# For backward compatibility with direct test calls
|
|
127
|
-
|
|
127
|
+
logger.warning(f"Warning: 'parameters' key not found in request body. Using body directly.")
|
|
128
128
|
params = body
|
|
129
129
|
|
|
130
130
|
# Extract auth data if available
|
|
131
131
|
auth_data = body.get("auth")
|
|
132
132
|
if auth_data:
|
|
133
|
-
|
|
133
|
+
logger.info(f"Auth data provided for provider: {auth_data.get('provider', 'unknown')}")
|
|
134
134
|
|
|
135
135
|
# Extract environment data if available
|
|
136
136
|
environment = body.get("environment", {})
|
|
137
137
|
if environment:
|
|
138
|
-
|
|
138
|
+
logger.info(f"Environment data provided: {environment}")
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
logger.info(f"Extracted parameters: {params}")
|
|
141
141
|
|
|
142
142
|
# Get the parameter model from handler's signature
|
|
143
143
|
sig = inspect.signature(handler)
|
|
@@ -166,12 +166,12 @@ class ToolsService:
|
|
|
166
166
|
|
|
167
167
|
result = await handler(*args, **kwargs)
|
|
168
168
|
|
|
169
|
-
|
|
169
|
+
logger.info(f"Tool {name} returned: {result}")
|
|
170
170
|
return result
|
|
171
171
|
except Exception as e:
|
|
172
172
|
import traceback
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
logger.error(f"Error in tool {name}: {str(e)}")
|
|
174
|
+
logger.error(traceback.format_exc())
|
|
175
175
|
raise HTTPException(status_code=500, detail=str(e))
|
|
176
176
|
|
|
177
177
|
# Update the route function name and docstring
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
opal_tools_sdk/__init__.py,sha256=riYZkv0C1Pgwd3SpE9Zwxs3a1eXa26wrGfY1N1Tdjb4,242
|
|
2
|
+
opal_tools_sdk/_registry.py,sha256=YE3eD4kcS09QDe4RccBYAzXPo9znEU7fblrsB-g3o-Y,67
|
|
3
|
+
opal_tools_sdk/auth.py,sha256=9aMiZv6n6_iu7hQA0sKg4hgNr5DzYFFuP0SWUoZf_Vw,1520
|
|
4
|
+
opal_tools_sdk/decorators.py,sha256=iHal7RElOSCWjqKHIK-Hc4GKOsjuGyUg0_lsQKVQShg,5315
|
|
5
|
+
opal_tools_sdk/logging.py,sha256=krvkhdql2GWh0aCWj5gfqyn5plsyLJ773WzIirK7tkU,1325
|
|
6
|
+
opal_tools_sdk/models.py,sha256=lVumBjEWbKtkRBzwXt7m5uagqinJ6Y96K9a5Uw39yaM,2092
|
|
7
|
+
opal_tools_sdk/service.py,sha256=wVYe40zEZc1ynIkvowD9APU8BFHQ5fdy3j8qlsdZB1U,6965
|
|
8
|
+
optimizely_opal_opal_tools_sdk-0.1.4.dev0.dist-info/METADATA,sha256=3uy9B3LYQM6kJj-D_mtfrTYqCmpogXeKT-cMWE8IySU,4115
|
|
9
|
+
optimizely_opal_opal_tools_sdk-0.1.4.dev0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
+
optimizely_opal_opal_tools_sdk-0.1.4.dev0.dist-info/top_level.txt,sha256=nCJ5PxF0rgoV6yNJvvuUaZPx4D3EWkl7gpu-6xafH1E,15
|
|
11
|
+
optimizely_opal_opal_tools_sdk-0.1.4.dev0.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
opal_tools_sdk/__init__.py,sha256=Eu141xM3QVq1O_BLGZS9m_asU1b_KaOApFtwvaYll6E,170
|
|
2
|
-
opal_tools_sdk/_registry.py,sha256=YE3eD4kcS09QDe4RccBYAzXPo9znEU7fblrsB-g3o-Y,67
|
|
3
|
-
opal_tools_sdk/auth.py,sha256=9aMiZv6n6_iu7hQA0sKg4hgNr5DzYFFuP0SWUoZf_Vw,1520
|
|
4
|
-
opal_tools_sdk/decorators.py,sha256=uZ11AY5QUxhi61vfZ3iJTtFoXg-uA0cknx4EWTdDTCo,5276
|
|
5
|
-
opal_tools_sdk/models.py,sha256=fa2hhnZ2GTz6xqhGTNyqyIQj_rBE1UFQxsJ4KfApCiU,2123
|
|
6
|
-
opal_tools_sdk/service.py,sha256=KBqdBHYJ5wgxky0mSv0NJPbbzVhq4YSafgaTbsndTrE,6914
|
|
7
|
-
optimizely_opal_opal_tools_sdk-0.1.2.dev0.dist-info/METADATA,sha256=M7L5-Pegr5M7UpvXKjLvOMQPz9nBjMTQScWkB7Waqs0,4115
|
|
8
|
-
optimizely_opal_opal_tools_sdk-0.1.2.dev0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
-
optimizely_opal_opal_tools_sdk-0.1.2.dev0.dist-info/top_level.txt,sha256=nCJ5PxF0rgoV6yNJvvuUaZPx4D3EWkl7gpu-6xafH1E,15
|
|
10
|
-
optimizely_opal_opal_tools_sdk-0.1.2.dev0.dist-info/RECORD,,
|
|
File without changes
|