optimizely-opal.opal-tools-sdk 0.1.0.dev0__tar.gz → 0.1.10.dev0__tar.gz

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.
Files changed (22) hide show
  1. optimizely_opal_opal_tools_sdk-0.1.10.dev0/PKG-INFO +255 -0
  2. optimizely_opal_opal_tools_sdk-0.1.10.dev0/README.md +231 -0
  3. optimizely_opal_opal_tools_sdk-0.1.10.dev0/opal_tools_sdk/__init__.py +20 -0
  4. {optimizely_opal_opal_tools_sdk-0.1.0.dev0 → optimizely_opal_opal_tools_sdk-0.1.10.dev0}/opal_tools_sdk/decorators.py +40 -12
  5. optimizely_opal_opal_tools_sdk-0.1.10.dev0/opal_tools_sdk/logging.py +37 -0
  6. optimizely_opal_opal_tools_sdk-0.1.10.dev0/opal_tools_sdk/models.py +129 -0
  7. {optimizely_opal_opal_tools_sdk-0.1.0.dev0 → optimizely_opal_opal_tools_sdk-0.1.10.dev0}/opal_tools_sdk/service.py +55 -48
  8. optimizely_opal_opal_tools_sdk-0.1.10.dev0/optimizely_opal.opal_tools_sdk.egg-info/PKG-INFO +255 -0
  9. {optimizely_opal_opal_tools_sdk-0.1.0.dev0 → optimizely_opal_opal_tools_sdk-0.1.10.dev0}/optimizely_opal.opal_tools_sdk.egg-info/SOURCES.txt +2 -0
  10. optimizely_opal_opal_tools_sdk-0.1.10.dev0/pyproject.toml +33 -0
  11. {optimizely_opal_opal_tools_sdk-0.1.0.dev0 → optimizely_opal_opal_tools_sdk-0.1.10.dev0}/setup.py +1 -1
  12. optimizely_opal_opal_tools_sdk-0.1.0.dev0/PKG-INFO +0 -24
  13. optimizely_opal_opal_tools_sdk-0.1.0.dev0/README.md +0 -114
  14. optimizely_opal_opal_tools_sdk-0.1.0.dev0/opal_tools_sdk/__init__.py +0 -6
  15. optimizely_opal_opal_tools_sdk-0.1.0.dev0/opal_tools_sdk/models.py +0 -69
  16. optimizely_opal_opal_tools_sdk-0.1.0.dev0/optimizely_opal.opal_tools_sdk.egg-info/PKG-INFO +0 -24
  17. {optimizely_opal_opal_tools_sdk-0.1.0.dev0 → optimizely_opal_opal_tools_sdk-0.1.10.dev0}/opal_tools_sdk/_registry.py +0 -0
  18. {optimizely_opal_opal_tools_sdk-0.1.0.dev0 → optimizely_opal_opal_tools_sdk-0.1.10.dev0}/opal_tools_sdk/auth.py +0 -0
  19. {optimizely_opal_opal_tools_sdk-0.1.0.dev0 → optimizely_opal_opal_tools_sdk-0.1.10.dev0}/optimizely_opal.opal_tools_sdk.egg-info/dependency_links.txt +0 -0
  20. {optimizely_opal_opal_tools_sdk-0.1.0.dev0 → optimizely_opal_opal_tools_sdk-0.1.10.dev0}/optimizely_opal.opal_tools_sdk.egg-info/requires.txt +0 -0
  21. {optimizely_opal_opal_tools_sdk-0.1.0.dev0 → optimizely_opal_opal_tools_sdk-0.1.10.dev0}/optimizely_opal.opal_tools_sdk.egg-info/top_level.txt +0 -0
  22. {optimizely_opal_opal_tools_sdk-0.1.0.dev0 → optimizely_opal_opal_tools_sdk-0.1.10.dev0}/setup.cfg +0 -0
@@ -0,0 +1,255 @@
1
+ Metadata-Version: 2.4
2
+ Name: optimizely-opal.opal-tools-sdk
3
+ Version: 0.1.10.dev0
4
+ Summary: SDK for creating Opal-compatible tools services
5
+ Home-page: https://github.com/optimizely/opal-tools-sdk
6
+ Author: Optimizely
7
+ Author-email: Optimizely <opal-team@optimizely.com>
8
+ License: MIT
9
+ Project-URL: Homepage, https://github.com/optimizely/opal-tools-sdk
10
+ Project-URL: Bug Tracker, https://github.com/optimizely/opal-tools-sdk/issues
11
+ Keywords: opal,tools,sdk,ai,llm
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: fastapi>=0.100.0
19
+ Requires-Dist: pydantic>=2.0.0
20
+ Requires-Dist: httpx>=0.24.1
21
+ Dynamic: author
22
+ Dynamic: home-page
23
+ Dynamic: requires-python
24
+
25
+ # Opal Tools SDK for Python
26
+
27
+ This SDK simplifies the creation of tools services compatible with the Opal Tools Management Service.
28
+
29
+ ## Features
30
+
31
+ - Easy definition of tool functions with decorators
32
+ - Automatic generation of discovery endpoints
33
+ - Parameter validation and type checking
34
+ - Authentication helpers
35
+ - FastAPI integration
36
+ - Island components for interactive UI responses
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ pip install optimizely-opal.opal-tools-sdk
42
+ ```
43
+
44
+ Note: While the package is installed as `optimizely-opal.opal-tools-sdk`, you'll still import it in your code as `opal_tools_sdk`:
45
+
46
+ ```python
47
+ # Import using the package name
48
+ from opal_tools_sdk import ToolsService, tool, IslandResponse, IslandConfig
49
+ ```
50
+
51
+ ## Usage
52
+
53
+ ```python
54
+ from opal_tools_sdk import ToolsService, tool
55
+ from pydantic import BaseModel
56
+ from fastapi import FastAPI
57
+
58
+ app = FastAPI()
59
+ tools_service = ToolsService(app)
60
+
61
+ class WeatherParameters(BaseModel):
62
+ location: str
63
+ units: str = "metric"
64
+
65
+ @tool("get_weather", "Gets current weather for a location")
66
+ async def get_weather(parameters: WeatherParameters):
67
+ # Implementation...
68
+ return {"temperature": 22, "condition": "sunny"}
69
+
70
+ # Discovery endpoint is automatically created at /discovery
71
+ ```
72
+
73
+ ## Authentication
74
+
75
+ The SDK provides two ways to require authentication for your tools:
76
+
77
+ ### 1. Using the `@requires_auth` decorator
78
+
79
+ ```python
80
+ from opal_tools_sdk import ToolsService, tool, AuthData
81
+ from opal_tools_sdk.auth import requires_auth
82
+ from pydantic import BaseModel
83
+ from fastapi import FastAPI
84
+ from typing import Optional
85
+
86
+ app = FastAPI()
87
+ tools_service = ToolsService(app)
88
+
89
+ class CalendarParameters(BaseModel):
90
+ date: str
91
+ timezone: str = "UTC"
92
+
93
+ # Single authentication requirement
94
+ @requires_auth(provider="google", scope_bundle="calendar", required=True)
95
+ @tool("get_calendar_events", "Gets calendar events for a date")
96
+ async def get_calendar_events(parameters: CalendarParameters, auth_data: Optional[AuthData] = None):
97
+ # The auth_data parameter contains authentication information
98
+ if auth_data:
99
+ token = auth_data["credentials"]["access_token"]
100
+
101
+ # Use the token to make authenticated requests
102
+ # ...
103
+
104
+ return {"events": ["Meeting at 10:00", "Lunch at 12:00"]}
105
+
106
+ # Multiple authentication requirements (tool can work with either provider)
107
+ @requires_auth(provider="google", scope_bundle="calendar", required=True)
108
+ @requires_auth(provider="microsoft", scope_bundle="outlook", required=True)
109
+ @tool("get_calendar_availability", "Check calendar availability")
110
+ async def get_calendar_availability(parameters: CalendarParameters, auth_data: Optional[AuthData] = None):
111
+ provider = ""
112
+ token = ""
113
+
114
+ if auth_data:
115
+ provider = auth_data["provider"]
116
+ token = auth_data["credentials"]["access_token"]
117
+
118
+ if provider == "google":
119
+ # Use Google Calendar API
120
+ pass
121
+ elif provider == "microsoft":
122
+ # Use Microsoft Outlook API
123
+ pass
124
+
125
+ return {"available": True, "provider_used": provider}
126
+ ```
127
+
128
+ ### 2. Specifying auth requirements in the `@tool` decorator
129
+
130
+ ```python
131
+ @tool(
132
+ "get_email",
133
+ "Gets emails from the user's inbox",
134
+ auth_requirements=[
135
+ {"provider": "google", "scope_bundle": "gmail", "required": True}
136
+ ]
137
+ )
138
+ async def get_email(parameters: EmailParameters, auth_data: Optional[AuthData] = None):
139
+ # Implementation...
140
+ return {"emails": ["Email 1", "Email 2"]}
141
+ ```
142
+
143
+ ## Island Components
144
+
145
+ The SDK includes Island components for creating interactive UI responses that allow users to input data and trigger actions.
146
+
147
+ ### Weather Tool with Interactive Island
148
+
149
+ ```python
150
+ from opal_tools_sdk import ToolsService, tool, IslandResponse, IslandConfig
151
+ from pydantic import BaseModel
152
+ from fastapi import FastAPI
153
+
154
+ app = FastAPI()
155
+ tools_service = ToolsService(app)
156
+
157
+ class WeatherParameters(BaseModel):
158
+ location: str
159
+ units: str = "metric"
160
+
161
+ @tool("get_weather", "Gets current weather for a location")
162
+ async def get_weather(parameters: WeatherParameters):
163
+ # Get weather data (implementation details omitted)
164
+ weather_data = {"temperature": 22, "condition": "sunny", "humidity": 65}
165
+
166
+ # Create an interactive island for weather settings
167
+ island = IslandConfig(
168
+ fields=[
169
+ IslandConfig.Field(
170
+ name="location",
171
+ label="Location",
172
+ type="string",
173
+ value=parameters.location
174
+ ),
175
+ IslandConfig.Field(
176
+ name="units",
177
+ label="Temperature Units",
178
+ type="string",
179
+ value=parameters.units,
180
+ options=["metric", "imperial", "kelvin"]
181
+ ),
182
+ IslandConfig.Field(
183
+ name="current_temp",
184
+ label="Current Temperature",
185
+ type="string",
186
+ value=f"{weather_data['temperature']}°{'C' if parameters.units == 'metric' else 'F'}"
187
+ )
188
+ ],
189
+ actions=[
190
+ IslandConfig.Action(
191
+ name="refresh_weather",
192
+ label="Refresh Weather",
193
+ type="button",
194
+ endpoint="/tools/get_weather",
195
+ operation="update"
196
+ )
197
+ ]
198
+ )
199
+
200
+ return IslandResponse.create([island])
201
+ ```
202
+
203
+ ### Island Components
204
+
205
+ #### IslandConfig.Field
206
+ Fields represent data inputs in the UI:
207
+ - `name`: Programmatic field identifier
208
+ - `label`: Human-readable label
209
+ - `type`: Field type (`"string"`, `"boolean"`, `"json"`)
210
+ - `value`: Current field value (optional)
211
+ - `hidden`: Whether to hide from user (optional, default: False)
212
+ - `options`: Available options for selection (optional)
213
+
214
+ #### IslandConfig.Action
215
+ Actions represent buttons or operations:
216
+ - `name`: Programmatic action identifier
217
+ - `label`: Human-readable button label
218
+ - `type`: UI element type (typically `"button"`)
219
+ - `endpoint`: API endpoint to call
220
+ - `operation`: Operation type (default: `"create"`)
221
+
222
+ #### IslandConfig
223
+ Contains the complete island configuration:
224
+ - `fields`: List of IslandConfig.Field objects
225
+ - `actions`: List of IslandConfig.Action objects
226
+ - `type`: Island type for UI rendering (optional, default: `None`)
227
+ - `icon`: Icon to display in the island (optional, default: `None`)
228
+
229
+ #### IslandResponse
230
+ The response wrapper for islands:
231
+ - Use `IslandResponse.create([islands])` to create responses
232
+ - Supports multiple islands per response
233
+
234
+ ## Type Definitions
235
+
236
+ The SDK provides several TypedDict and dataclass definitions for better type safety:
237
+
238
+ ### Authentication Types
239
+ - `AuthData`: TypedDict containing provider and credentials information
240
+ - `Credentials`: TypedDict with access_token, org_sso_id, customer_id, instance_id, and product_sku
241
+ - `AuthRequirement`: Dataclass for specifying authentication requirements
242
+
243
+ ### Execution Environment
244
+ - `Environment`: TypedDict specifying execution mode (`"headless"` or `"interactive"`)
245
+
246
+ ### Parameter Types
247
+ - `ParameterType`: Enum for supported parameter types (string, integer, number, boolean, list, dictionary)
248
+ - `Parameter`: Dataclass for tool parameter definitions
249
+ - `Function`: Dataclass for complete tool function definitions
250
+
251
+ These types are automatically imported when you import from `opal_tools_sdk` and provide better IDE support and type checking.
252
+
253
+ ## Documentation
254
+
255
+ See full documentation for more examples and configuration options.
@@ -0,0 +1,231 @@
1
+ # Opal Tools SDK for Python
2
+
3
+ This SDK simplifies the creation of tools services compatible with the Opal Tools Management Service.
4
+
5
+ ## Features
6
+
7
+ - Easy definition of tool functions with decorators
8
+ - Automatic generation of discovery endpoints
9
+ - Parameter validation and type checking
10
+ - Authentication helpers
11
+ - FastAPI integration
12
+ - Island components for interactive UI responses
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pip install optimizely-opal.opal-tools-sdk
18
+ ```
19
+
20
+ Note: While the package is installed as `optimizely-opal.opal-tools-sdk`, you'll still import it in your code as `opal_tools_sdk`:
21
+
22
+ ```python
23
+ # Import using the package name
24
+ from opal_tools_sdk import ToolsService, tool, IslandResponse, IslandConfig
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```python
30
+ from opal_tools_sdk import ToolsService, tool
31
+ from pydantic import BaseModel
32
+ from fastapi import FastAPI
33
+
34
+ app = FastAPI()
35
+ tools_service = ToolsService(app)
36
+
37
+ class WeatherParameters(BaseModel):
38
+ location: str
39
+ units: str = "metric"
40
+
41
+ @tool("get_weather", "Gets current weather for a location")
42
+ async def get_weather(parameters: WeatherParameters):
43
+ # Implementation...
44
+ return {"temperature": 22, "condition": "sunny"}
45
+
46
+ # Discovery endpoint is automatically created at /discovery
47
+ ```
48
+
49
+ ## Authentication
50
+
51
+ The SDK provides two ways to require authentication for your tools:
52
+
53
+ ### 1. Using the `@requires_auth` decorator
54
+
55
+ ```python
56
+ from opal_tools_sdk import ToolsService, tool, AuthData
57
+ from opal_tools_sdk.auth import requires_auth
58
+ from pydantic import BaseModel
59
+ from fastapi import FastAPI
60
+ from typing import Optional
61
+
62
+ app = FastAPI()
63
+ tools_service = ToolsService(app)
64
+
65
+ class CalendarParameters(BaseModel):
66
+ date: str
67
+ timezone: str = "UTC"
68
+
69
+ # Single authentication requirement
70
+ @requires_auth(provider="google", scope_bundle="calendar", required=True)
71
+ @tool("get_calendar_events", "Gets calendar events for a date")
72
+ async def get_calendar_events(parameters: CalendarParameters, auth_data: Optional[AuthData] = None):
73
+ # The auth_data parameter contains authentication information
74
+ if auth_data:
75
+ token = auth_data["credentials"]["access_token"]
76
+
77
+ # Use the token to make authenticated requests
78
+ # ...
79
+
80
+ return {"events": ["Meeting at 10:00", "Lunch at 12:00"]}
81
+
82
+ # Multiple authentication requirements (tool can work with either provider)
83
+ @requires_auth(provider="google", scope_bundle="calendar", required=True)
84
+ @requires_auth(provider="microsoft", scope_bundle="outlook", required=True)
85
+ @tool("get_calendar_availability", "Check calendar availability")
86
+ async def get_calendar_availability(parameters: CalendarParameters, auth_data: Optional[AuthData] = None):
87
+ provider = ""
88
+ token = ""
89
+
90
+ if auth_data:
91
+ provider = auth_data["provider"]
92
+ token = auth_data["credentials"]["access_token"]
93
+
94
+ if provider == "google":
95
+ # Use Google Calendar API
96
+ pass
97
+ elif provider == "microsoft":
98
+ # Use Microsoft Outlook API
99
+ pass
100
+
101
+ return {"available": True, "provider_used": provider}
102
+ ```
103
+
104
+ ### 2. Specifying auth requirements in the `@tool` decorator
105
+
106
+ ```python
107
+ @tool(
108
+ "get_email",
109
+ "Gets emails from the user's inbox",
110
+ auth_requirements=[
111
+ {"provider": "google", "scope_bundle": "gmail", "required": True}
112
+ ]
113
+ )
114
+ async def get_email(parameters: EmailParameters, auth_data: Optional[AuthData] = None):
115
+ # Implementation...
116
+ return {"emails": ["Email 1", "Email 2"]}
117
+ ```
118
+
119
+ ## Island Components
120
+
121
+ The SDK includes Island components for creating interactive UI responses that allow users to input data and trigger actions.
122
+
123
+ ### Weather Tool with Interactive Island
124
+
125
+ ```python
126
+ from opal_tools_sdk import ToolsService, tool, IslandResponse, IslandConfig
127
+ from pydantic import BaseModel
128
+ from fastapi import FastAPI
129
+
130
+ app = FastAPI()
131
+ tools_service = ToolsService(app)
132
+
133
+ class WeatherParameters(BaseModel):
134
+ location: str
135
+ units: str = "metric"
136
+
137
+ @tool("get_weather", "Gets current weather for a location")
138
+ async def get_weather(parameters: WeatherParameters):
139
+ # Get weather data (implementation details omitted)
140
+ weather_data = {"temperature": 22, "condition": "sunny", "humidity": 65}
141
+
142
+ # Create an interactive island for weather settings
143
+ island = IslandConfig(
144
+ fields=[
145
+ IslandConfig.Field(
146
+ name="location",
147
+ label="Location",
148
+ type="string",
149
+ value=parameters.location
150
+ ),
151
+ IslandConfig.Field(
152
+ name="units",
153
+ label="Temperature Units",
154
+ type="string",
155
+ value=parameters.units,
156
+ options=["metric", "imperial", "kelvin"]
157
+ ),
158
+ IslandConfig.Field(
159
+ name="current_temp",
160
+ label="Current Temperature",
161
+ type="string",
162
+ value=f"{weather_data['temperature']}°{'C' if parameters.units == 'metric' else 'F'}"
163
+ )
164
+ ],
165
+ actions=[
166
+ IslandConfig.Action(
167
+ name="refresh_weather",
168
+ label="Refresh Weather",
169
+ type="button",
170
+ endpoint="/tools/get_weather",
171
+ operation="update"
172
+ )
173
+ ]
174
+ )
175
+
176
+ return IslandResponse.create([island])
177
+ ```
178
+
179
+ ### Island Components
180
+
181
+ #### IslandConfig.Field
182
+ Fields represent data inputs in the UI:
183
+ - `name`: Programmatic field identifier
184
+ - `label`: Human-readable label
185
+ - `type`: Field type (`"string"`, `"boolean"`, `"json"`)
186
+ - `value`: Current field value (optional)
187
+ - `hidden`: Whether to hide from user (optional, default: False)
188
+ - `options`: Available options for selection (optional)
189
+
190
+ #### IslandConfig.Action
191
+ Actions represent buttons or operations:
192
+ - `name`: Programmatic action identifier
193
+ - `label`: Human-readable button label
194
+ - `type`: UI element type (typically `"button"`)
195
+ - `endpoint`: API endpoint to call
196
+ - `operation`: Operation type (default: `"create"`)
197
+
198
+ #### IslandConfig
199
+ Contains the complete island configuration:
200
+ - `fields`: List of IslandConfig.Field objects
201
+ - `actions`: List of IslandConfig.Action objects
202
+ - `type`: Island type for UI rendering (optional, default: `None`)
203
+ - `icon`: Icon to display in the island (optional, default: `None`)
204
+
205
+ #### IslandResponse
206
+ The response wrapper for islands:
207
+ - Use `IslandResponse.create([islands])` to create responses
208
+ - Supports multiple islands per response
209
+
210
+ ## Type Definitions
211
+
212
+ The SDK provides several TypedDict and dataclass definitions for better type safety:
213
+
214
+ ### Authentication Types
215
+ - `AuthData`: TypedDict containing provider and credentials information
216
+ - `Credentials`: TypedDict with access_token, org_sso_id, customer_id, instance_id, and product_sku
217
+ - `AuthRequirement`: Dataclass for specifying authentication requirements
218
+
219
+ ### Execution Environment
220
+ - `Environment`: TypedDict specifying execution mode (`"headless"` or `"interactive"`)
221
+
222
+ ### Parameter Types
223
+ - `ParameterType`: Enum for supported parameter types (string, integer, number, boolean, list, dictionary)
224
+ - `Parameter`: Dataclass for tool parameter definitions
225
+ - `Function`: Dataclass for complete tool function definitions
226
+
227
+ These types are automatically imported when you import from `opal_tools_sdk` and provide better IDE support and type checking.
228
+
229
+ ## Documentation
230
+
231
+ See full documentation for more examples and configuration options.
@@ -0,0 +1,20 @@
1
+ from .service import ToolsService
2
+ from .decorators import tool
3
+ from .auth import requires_auth
4
+ from .logging import register_logger_factory
5
+ from .models import AuthData, AuthRequirement, Credentials, Environment, IslandConfig, IslandResponse
6
+
7
+ __version__ = "0.1.0"
8
+ __all__ = [
9
+ "ToolsService",
10
+ "tool",
11
+ "requires_auth",
12
+ "register_logger_factory",
13
+ # Models
14
+ "AuthData",
15
+ "AuthRequirement",
16
+ "Credentials",
17
+ "Environment",
18
+ "IslandConfig",
19
+ "IslandResponse",
20
+ ]
@@ -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 = logging.getLogger("opal_tools_sdk")
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.
@@ -61,21 +62,48 @@ def tool(name: str, description: str, auth_requirements: Optional[List[Dict[str,
61
62
  else:
62
63
  field_type = str
63
64
 
65
+ # Check if the field is Optional (Union with None)
66
+ # Optional[X] is equivalent to Union[X, None]
67
+ type_args = getattr(field_type, '__args__', ())
68
+ is_optional = get_origin(field_type) is Union and type(None) in type_args
69
+
70
+ # Extract the actual type from Optional[T]
71
+ if is_optional and type_args:
72
+ # Get the non-None type from Union[T, None]
73
+ field_type = next(
74
+ (arg for arg in type_args if arg is not type(None)),
75
+ field_type,
76
+ )
77
+
64
78
  # Map Python type to Parameter type
65
79
  param_type = ParameterType.string
66
- if field_type == int:
80
+ if field_type is int:
67
81
  param_type = ParameterType.integer
68
- elif field_type == float:
82
+ elif field_type is float:
69
83
  param_type = ParameterType.number
70
- elif field_type == bool:
84
+ elif field_type is bool:
71
85
  param_type = ParameterType.boolean
72
- elif field_type == list or field_type == List:
86
+ elif field_type is list or get_origin(field_type) is list:
73
87
  param_type = ParameterType.list
74
- elif field_type == dict or field_type == Dict:
88
+ elif field_type is dict or get_origin(field_type) is dict:
75
89
  param_type = ParameterType.dictionary
76
90
 
77
91
  # Determine if required
78
- required = field_info.default is ... if hasattr(field_info, 'default') else True
92
+ field_info_extra = getattr(field_info, "json_schema_extra") or {}
93
+ if "required" in field_info_extra:
94
+ required = field_info_extra["required"]
95
+ # If the field is typed as Optional, it's not required (check this FIRST)
96
+ elif is_optional:
97
+ required = False
98
+ # Check for Pydantic v2 is_required() method
99
+ elif hasattr(field_info, 'is_required'):
100
+ required = field_info.is_required()
101
+ # Fall back to checking if default is ... (Pydantic v1/v2 compatibility)
102
+ elif hasattr(field_info, 'default'):
103
+ required = field_info.default is ...
104
+ else:
105
+ # If no default attribute at all, assume required
106
+ required = True
79
107
 
80
108
  # Get description
81
109
  description_text = ""
@@ -91,9 +119,9 @@ def tool(name: str, description: str, auth_requirements: Optional[List[Dict[str,
91
119
  required=required
92
120
  ))
93
121
 
94
- print(f"Registered parameter: {field_name} of type {param_type.value}, required: {required}")
122
+ logger.info(f"Registered parameter: {field_name} of type {param_type.value}, required: {required}")
95
123
  else:
96
- print(f"Warning: No parameter model found for {name}")
124
+ logger.warning(f"Warning: No parameter model found for {name}")
97
125
 
98
126
  endpoint = f"/tools/{name}"
99
127
 
@@ -108,10 +136,10 @@ def tool(name: str, description: str, auth_requirements: Optional[List[Dict[str,
108
136
  required=auth_req.get("required", True)
109
137
  ))
110
138
 
111
- print(f"Registering tool {name} with endpoint {endpoint}")
139
+ logger.info(f"Registering tool {name} with endpoint {endpoint}")
112
140
 
113
141
  if not _registry.services:
114
- print("No services registered in registry! Make sure to create ToolsService before decorating functions.")
142
+ logger.warning("No services registered in registry! Make sure to create ToolsService before decorating functions.")
115
143
 
116
144
  for service in _registry.services:
117
145
  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