wiil-python 0.0.2__tar.gz → 0.0.3__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.
- {wiil_python-0.0.2 → wiil_python-0.0.3}/PKG-INFO +1 -1
- {wiil_python-0.0.2 → wiil_python-0.0.3}/pyproject.toml +1 -1
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/client/http_client.py +156 -63
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/client/types.py +16 -36
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/account/organizations.py +1 -4
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/account/projects.py +13 -15
- wiil_python-0.0.3/wiil/resources/business_mgt/business_services.py +145 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/business_mgt/customers.py +73 -8
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/business_mgt/menu_orders.py +20 -7
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/business_mgt/menus.py +167 -16
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/business_mgt/product_orders.py +20 -7
- wiil_python-0.0.3/wiil/resources/business_mgt/products.py +280 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/business_mgt/property_config.py +215 -17
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/business_mgt/property_inquiry.py +22 -7
- wiil_python-0.0.3/wiil/resources/business_mgt/reservation_resources.py +147 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/business_mgt/reservations.py +35 -11
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/business_mgt/service_appointments.py +27 -8
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/agent_configs.py +9 -4
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/conversation_configs.py +8 -2
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/deployment_channels.py +17 -6
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/deployment_configs.py +22 -8
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/dynamic_agent_status.py +4 -1
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/dynamic_phone_agent.py +12 -4
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/dynamic_web_agent.py +12 -4
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/instruction_configs.py +13 -5
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/knowledge_sources.py +5 -2
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/phone_configs.py +15 -5
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/provisioning_configs.py +20 -6
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/support_models.py +14 -12
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/telephony_provider.py +13 -3
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/translation_sessions.py +8 -2
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/services/translation/service.py +1 -1
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil_python.egg-info/PKG-INFO +1 -1
- wiil_python-0.0.2/wiil/resources/business_mgt/business_services.py +0 -80
- wiil_python-0.0.2/wiil/resources/business_mgt/products.py +0 -142
- wiil_python-0.0.2/wiil/resources/business_mgt/reservation_resources.py +0 -76
- {wiil_python-0.0.2 → wiil_python-0.0.3}/README.md +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/setup.cfg +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/setup.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/__init__.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/client/__init__.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/client/async_wiil_client.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/client/wiil_client.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/client/will_service.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/errors/__init__.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/errors/exceptions.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/__init__.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/account/__init__.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/account/organization.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/account/project.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/account/supported_business_verticals.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/assistant_setups/__init__.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/assistant_setups/assistant_setup_result.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/assistant_setups/base_assistant_setup.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/assistant_setups/phone_assistant_setup.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/assistant_setups/web_assistant_setup.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/base.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/__init__.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/appointment_additional_info.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/appointment_field_config.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/customer.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/menu_config.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/menu_order.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/order.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/product_config.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/product_order.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/property_config.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/property_inquiry.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/reservation.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/reservation_resource.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/service_appointment.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/service_config.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/service_person.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/conversation/__init__.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/conversation/conversation_config.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/conversation/conversation_message.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/conversation/translation_config.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/conversation/translation_conversation.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/request/__init__.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/request/paginated_query.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/request/paginated_result.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/__init__.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/agent_config.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/call_transfer_config.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/deployment_config.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/dynamic_setup/__init__.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/dynamic_setup/base_agent_setup.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/dynamic_setup/phone_agent_setup.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/dynamic_setup/web_agent_setup.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/instruction_config.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/interaction_channels.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/knowledge.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/phone_config.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/phone_number.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/provisioning_config.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/support_llm.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/voice_language.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/type_definitions/__init__.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/type_definitions/account_definitions.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/type_definitions/business_definitions.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/type_definitions/conversation_definitions.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/type_definitions/dynamic_fields/__init__.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/type_definitions/dynamic_fields/field_definition.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/type_definitions/dynamic_fields/field_types.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/type_definitions/knowledge_definitions.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/type_definitions/service_config_definitions.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/py.typed +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/__init__.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/account/__init__.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/business_mgt/__init__.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/__init__.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/services/__init__.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/services/ott/__init__.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/services/ott/models.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/services/ott/service.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/services/translation/__init__.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/services/translation/models.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/types/__init__.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/types/account_types.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/types/business_types.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/types/conversation_types.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/types/knowledge_types.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/types/paginated_quest.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/types/paginated_result.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/types/service_types.py +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil_python.egg-info/SOURCES.txt +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil_python.egg-info/dependency_links.txt +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil_python.egg-info/requires.txt +0 -0
- {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil_python.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wiil-python
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: Official Python SDK for WIIL Platform - AI-powered conversational services for intelligent customer interactions, voice processing, real-time translation, and business management
|
|
5
5
|
Author-email: WIIL <dev-support@wiil.io>
|
|
6
6
|
License: MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "wiil-python"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.3"
|
|
8
8
|
description = "Official Python SDK for WIIL Platform - AI-powered conversational services for intelligent customer interactions, voice processing, real-time translation, and business management"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -12,7 +12,7 @@ Example:
|
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
import json
|
|
15
|
-
from typing import Any, Dict, Optional, Type, TypeVar
|
|
15
|
+
from typing import Any, Dict, List, Optional, Type, TypeVar, Union, get_origin, get_args
|
|
16
16
|
|
|
17
17
|
import requests
|
|
18
18
|
from pydantic import BaseModel, ValidationError
|
|
@@ -66,27 +66,35 @@ class HttpClient:
|
|
|
66
66
|
# Create a session for connection pooling
|
|
67
67
|
self.session = requests.Session()
|
|
68
68
|
self.session.headers.update({
|
|
69
|
-
'
|
|
70
|
-
'X-WIIL-API-Key': self.api_key,
|
|
69
|
+
'X-Wiil-Api-Key': self.api_key,
|
|
71
70
|
})
|
|
72
71
|
|
|
73
|
-
def get(
|
|
72
|
+
def get(
|
|
73
|
+
self,
|
|
74
|
+
path: str,
|
|
75
|
+
response_model: Optional[Type[T]] = None,
|
|
76
|
+
**kwargs: Any
|
|
77
|
+
) -> T:
|
|
74
78
|
"""Make a GET request to the API.
|
|
75
79
|
|
|
76
80
|
Args:
|
|
77
81
|
path: API endpoint path (e.g., '/organizations')
|
|
82
|
+
response_model: Optional Pydantic model to parse response into
|
|
78
83
|
**kwargs: Additional keyword arguments to pass to requests.get()
|
|
79
84
|
|
|
80
85
|
Returns:
|
|
81
|
-
The response data
|
|
86
|
+
The response data parsed into response_model if provided,
|
|
87
|
+
otherwise AttrDict
|
|
82
88
|
|
|
83
89
|
Raises:
|
|
84
90
|
WiilAPIError: When the API returns an error response (4xx or 5xx)
|
|
85
91
|
WiilNetworkError: When network communication fails
|
|
92
|
+
WiilValidationError: When response validation fails
|
|
86
93
|
|
|
87
94
|
Example:
|
|
88
|
-
>>>
|
|
89
|
-
>>>
|
|
95
|
+
>>> from wiil.models import Organization
|
|
96
|
+
>>> org = http.get('/organizations/123', response_model=Organization)
|
|
97
|
+
>>> print(org.id)
|
|
90
98
|
"""
|
|
91
99
|
url = f"{self.base_url}{path}"
|
|
92
100
|
|
|
@@ -101,11 +109,14 @@ class HttpClient:
|
|
|
101
109
|
# Parse the response
|
|
102
110
|
response_data = response.json()
|
|
103
111
|
|
|
104
|
-
#
|
|
112
|
+
# Check for API-level failure (success: false)
|
|
113
|
+
self._check_api_success(response_data, response.status_code)
|
|
114
|
+
|
|
115
|
+
# Extract data from APIResponse wrapper
|
|
105
116
|
if isinstance(response_data, dict) and 'data' in response_data:
|
|
106
|
-
return self.
|
|
117
|
+
return self._parse_response(response_data['data'], response_model)
|
|
107
118
|
|
|
108
|
-
return self.
|
|
119
|
+
return self._parse_response(response_data, response_model)
|
|
109
120
|
|
|
110
121
|
except Timeout:
|
|
111
122
|
raise WiilNetworkError(
|
|
@@ -135,32 +146,34 @@ class HttpClient:
|
|
|
135
146
|
path: str,
|
|
136
147
|
data: Any,
|
|
137
148
|
schema: Optional[Type[BaseModel]] = None,
|
|
149
|
+
response_model: Optional[Type[T]] = None,
|
|
138
150
|
**kwargs: Any
|
|
139
|
-
) ->
|
|
151
|
+
) -> T:
|
|
140
152
|
"""Make a POST request to the API with optional validation.
|
|
141
153
|
|
|
142
154
|
Args:
|
|
143
155
|
path: API endpoint path
|
|
144
156
|
data: Request payload (will be JSON-encoded)
|
|
145
157
|
schema: Optional Pydantic model for validating the request payload
|
|
158
|
+
response_model: Optional Pydantic model to parse response into
|
|
146
159
|
**kwargs: Additional keyword arguments to pass to requests.post()
|
|
147
160
|
|
|
148
161
|
Returns:
|
|
149
|
-
The response data
|
|
162
|
+
The response data parsed into response_model if provided,
|
|
163
|
+
otherwise AttrDict
|
|
150
164
|
|
|
151
165
|
Raises:
|
|
152
|
-
WiilValidationError: When request validation fails
|
|
166
|
+
WiilValidationError: When request or response validation fails
|
|
153
167
|
WiilAPIError: When the API returns an error response
|
|
154
168
|
WiilNetworkError: When network communication fails
|
|
155
169
|
|
|
156
170
|
Example:
|
|
157
|
-
>>> from
|
|
158
|
-
>>>
|
|
159
|
-
... name: str
|
|
160
|
-
>>> data = http.post(
|
|
171
|
+
>>> from wiil.models import Organization, CreateOrganization
|
|
172
|
+
>>> org = http.post(
|
|
161
173
|
... '/organizations',
|
|
162
174
|
... {'name': 'Acme Corp'},
|
|
163
|
-
... schema=
|
|
175
|
+
... schema=CreateOrganization,
|
|
176
|
+
... response_model=Organization
|
|
164
177
|
... )
|
|
165
178
|
"""
|
|
166
179
|
# Validate request if schema provided
|
|
@@ -191,11 +204,14 @@ class HttpClient:
|
|
|
191
204
|
# Parse the response
|
|
192
205
|
response_data = response.json()
|
|
193
206
|
|
|
194
|
-
#
|
|
207
|
+
# Check for API-level failure (success: false)
|
|
208
|
+
self._check_api_success(response_data, response.status_code)
|
|
209
|
+
|
|
210
|
+
# Extract data from APIResponse wrapper and parse
|
|
195
211
|
if isinstance(response_data, dict) and 'data' in response_data:
|
|
196
|
-
return self.
|
|
212
|
+
return self._parse_response(response_data['data'], response_model)
|
|
197
213
|
|
|
198
|
-
return self.
|
|
214
|
+
return self._parse_response(response_data, response_model)
|
|
199
215
|
|
|
200
216
|
except Timeout:
|
|
201
217
|
raise WiilNetworkError(
|
|
@@ -225,28 +241,33 @@ class HttpClient:
|
|
|
225
241
|
path: str,
|
|
226
242
|
data: Any,
|
|
227
243
|
schema: Optional[Type[BaseModel]] = None,
|
|
244
|
+
response_model: Optional[Type[T]] = None,
|
|
228
245
|
**kwargs: Any
|
|
229
|
-
) ->
|
|
246
|
+
) -> T:
|
|
230
247
|
"""Make a PUT request to the API with optional validation.
|
|
231
248
|
|
|
232
249
|
Args:
|
|
233
250
|
path: API endpoint path
|
|
234
251
|
data: Request payload (will be JSON-encoded)
|
|
235
252
|
schema: Optional Pydantic model for validating the request payload
|
|
253
|
+
response_model: Optional Pydantic model to parse response into
|
|
236
254
|
**kwargs: Additional keyword arguments to pass to requests.put()
|
|
237
255
|
|
|
238
256
|
Returns:
|
|
239
|
-
The response data
|
|
257
|
+
The response data parsed into response_model if provided,
|
|
258
|
+
otherwise AttrDict
|
|
240
259
|
|
|
241
260
|
Raises:
|
|
242
|
-
WiilValidationError: When request validation fails
|
|
261
|
+
WiilValidationError: When request or response validation fails
|
|
243
262
|
WiilAPIError: When the API returns an error response
|
|
244
263
|
WiilNetworkError: When network communication fails
|
|
245
264
|
|
|
246
265
|
Example:
|
|
247
|
-
>>>
|
|
266
|
+
>>> from wiil.models import Organization
|
|
267
|
+
>>> org = http.put(
|
|
248
268
|
... '/organizations/org_123',
|
|
249
|
-
... {'name': 'Acme Corporation'}
|
|
269
|
+
... {'name': 'Acme Corporation'},
|
|
270
|
+
... response_model=Organization
|
|
250
271
|
... )
|
|
251
272
|
"""
|
|
252
273
|
# Validate request if schema provided
|
|
@@ -277,11 +298,14 @@ class HttpClient:
|
|
|
277
298
|
# Parse the response
|
|
278
299
|
response_data = response.json()
|
|
279
300
|
|
|
280
|
-
#
|
|
301
|
+
# Check for API-level failure (success: false)
|
|
302
|
+
self._check_api_success(response_data, response.status_code)
|
|
303
|
+
|
|
304
|
+
# Extract data from APIResponse wrapper and parse
|
|
281
305
|
if isinstance(response_data, dict) and 'data' in response_data:
|
|
282
|
-
return self.
|
|
306
|
+
return self._parse_response(response_data['data'], response_model)
|
|
283
307
|
|
|
284
|
-
return self.
|
|
308
|
+
return self._parse_response(response_data, response_model)
|
|
285
309
|
|
|
286
310
|
except Timeout:
|
|
287
311
|
raise WiilNetworkError(
|
|
@@ -311,28 +335,33 @@ class HttpClient:
|
|
|
311
335
|
path: str,
|
|
312
336
|
data: Any,
|
|
313
337
|
schema: Optional[Type[BaseModel]] = None,
|
|
338
|
+
response_model: Optional[Type[T]] = None,
|
|
314
339
|
**kwargs: Any
|
|
315
|
-
) ->
|
|
340
|
+
) -> T:
|
|
316
341
|
"""Make a PATCH request to the API with optional validation.
|
|
317
342
|
|
|
318
343
|
Args:
|
|
319
344
|
path: API endpoint path
|
|
320
345
|
data: Request payload (will be JSON-encoded)
|
|
321
346
|
schema: Optional Pydantic model for validating the request payload
|
|
347
|
+
response_model: Optional Pydantic model to parse response into
|
|
322
348
|
**kwargs: Additional keyword arguments to pass to requests.patch()
|
|
323
349
|
|
|
324
350
|
Returns:
|
|
325
|
-
The response data
|
|
351
|
+
The response data parsed into response_model if provided,
|
|
352
|
+
otherwise AttrDict
|
|
326
353
|
|
|
327
354
|
Raises:
|
|
328
|
-
WiilValidationError: When request validation fails
|
|
355
|
+
WiilValidationError: When request or response validation fails
|
|
329
356
|
WiilAPIError: When the API returns an error response
|
|
330
357
|
WiilNetworkError: When network communication fails
|
|
331
358
|
|
|
332
359
|
Example:
|
|
333
|
-
>>>
|
|
360
|
+
>>> from wiil.models import Organization
|
|
361
|
+
>>> org = http.patch(
|
|
334
362
|
... '/organizations/org_123',
|
|
335
|
-
... {'name': 'Acme Corp Updated'}
|
|
363
|
+
... {'name': 'Acme Corp Updated'},
|
|
364
|
+
... response_model=Organization
|
|
336
365
|
... )
|
|
337
366
|
"""
|
|
338
367
|
# Validate request if schema provided
|
|
@@ -363,11 +392,14 @@ class HttpClient:
|
|
|
363
392
|
# Parse the response
|
|
364
393
|
response_data = response.json()
|
|
365
394
|
|
|
366
|
-
#
|
|
395
|
+
# Check for API-level failure (success: false)
|
|
396
|
+
self._check_api_success(response_data, response.status_code)
|
|
397
|
+
|
|
398
|
+
# Extract data from APIResponse wrapper and parse
|
|
367
399
|
if isinstance(response_data, dict) and 'data' in response_data:
|
|
368
|
-
return self.
|
|
400
|
+
return self._parse_response(response_data['data'], response_model)
|
|
369
401
|
|
|
370
|
-
return self.
|
|
402
|
+
return self._parse_response(response_data, response_model)
|
|
371
403
|
|
|
372
404
|
except Timeout:
|
|
373
405
|
raise WiilNetworkError(
|
|
@@ -392,19 +424,27 @@ class HttpClient:
|
|
|
392
424
|
details={'error': str(e)}
|
|
393
425
|
)
|
|
394
426
|
|
|
395
|
-
def delete(
|
|
427
|
+
def delete(
|
|
428
|
+
self,
|
|
429
|
+
path: str,
|
|
430
|
+
response_model: Optional[Type[T]] = None,
|
|
431
|
+
**kwargs: Any
|
|
432
|
+
) -> Optional[T]:
|
|
396
433
|
"""Make a DELETE request to the API.
|
|
397
434
|
|
|
398
435
|
Args:
|
|
399
436
|
path: API endpoint path
|
|
437
|
+
response_model: Optional Pydantic model to parse response into
|
|
400
438
|
**kwargs: Additional keyword arguments to pass to requests.delete()
|
|
401
439
|
|
|
402
440
|
Returns:
|
|
403
|
-
The response data
|
|
441
|
+
The response data parsed into response_model if provided,
|
|
442
|
+
otherwise AttrDict or None
|
|
404
443
|
|
|
405
444
|
Raises:
|
|
406
445
|
WiilAPIError: When the API returns an error response
|
|
407
446
|
WiilNetworkError: When network communication fails
|
|
447
|
+
WiilValidationError: When response validation fails
|
|
408
448
|
|
|
409
449
|
Example:
|
|
410
450
|
>>> http.delete('/organizations/org_123')
|
|
@@ -423,11 +463,14 @@ class HttpClient:
|
|
|
423
463
|
if response.text:
|
|
424
464
|
response_data = response.json()
|
|
425
465
|
|
|
426
|
-
#
|
|
466
|
+
# Check for API-level failure (success: false)
|
|
467
|
+
self._check_api_success(response_data, response.status_code)
|
|
468
|
+
|
|
469
|
+
# Extract data from APIResponse wrapper and parse
|
|
427
470
|
if isinstance(response_data, dict) and 'data' in response_data:
|
|
428
|
-
return self.
|
|
471
|
+
return self._parse_response(response_data['data'], response_model)
|
|
429
472
|
|
|
430
|
-
return self.
|
|
473
|
+
return self._parse_response(response_data, response_model)
|
|
431
474
|
|
|
432
475
|
return None
|
|
433
476
|
|
|
@@ -476,14 +519,13 @@ class HttpClient:
|
|
|
476
519
|
try:
|
|
477
520
|
error_data = response.json()
|
|
478
521
|
|
|
479
|
-
# Check if it's a standard WIIL API error response
|
|
522
|
+
# Check if it's a standard WIIL API error response (flat structure)
|
|
480
523
|
if isinstance(error_data, dict) and not error_data.get('success', True):
|
|
481
|
-
error_info = error_data.get('error', {})
|
|
482
524
|
return WiilAPIError(
|
|
483
|
-
message=
|
|
484
|
-
status_code=status_code,
|
|
485
|
-
code=
|
|
486
|
-
details=
|
|
525
|
+
message=error_data.get('message', f'Request failed with status {status_code}'),
|
|
526
|
+
status_code=error_data.get('status', status_code),
|
|
527
|
+
code=error_data.get('code', 'UNKNOWN_ERROR'),
|
|
528
|
+
details=error_data.get('meta')
|
|
487
529
|
)
|
|
488
530
|
|
|
489
531
|
except (json.JSONDecodeError, ValueError):
|
|
@@ -498,6 +540,28 @@ class HttpClient:
|
|
|
498
540
|
details={'response_text': response.text}
|
|
499
541
|
)
|
|
500
542
|
|
|
543
|
+
def _check_api_success(self, response_data: Any, status_code: int = 200) -> None:
|
|
544
|
+
"""Check if API response indicates failure even with 2xx status.
|
|
545
|
+
|
|
546
|
+
The WIIL API may return HTTP 200 with success=false for logical errors.
|
|
547
|
+
This method checks for that condition and raises WiilAPIError.
|
|
548
|
+
|
|
549
|
+
Args:
|
|
550
|
+
response_data: Parsed JSON response data
|
|
551
|
+
status_code: HTTP status code for error reporting
|
|
552
|
+
|
|
553
|
+
Raises:
|
|
554
|
+
WiilAPIError: When response has success=false
|
|
555
|
+
"""
|
|
556
|
+
if isinstance(response_data, dict) and response_data.get('success') is False:
|
|
557
|
+
# Extract error fields from flat structure (matches TypeScript SDK)
|
|
558
|
+
raise WiilAPIError(
|
|
559
|
+
message=response_data.get('message', 'Request failed'),
|
|
560
|
+
status_code=response_data.get('status', status_code),
|
|
561
|
+
code=response_data.get('code', 'API_ERROR'),
|
|
562
|
+
details=response_data.get('meta')
|
|
563
|
+
)
|
|
564
|
+
|
|
501
565
|
def _to_attr_obj(self, value: Any) -> Any:
|
|
502
566
|
"""Recursively convert dict payloads into attribute-accessible mappings."""
|
|
503
567
|
if isinstance(value, dict):
|
|
@@ -506,6 +570,47 @@ class HttpClient:
|
|
|
506
570
|
return [self._to_attr_obj(item) for item in value]
|
|
507
571
|
return value
|
|
508
572
|
|
|
573
|
+
def _parse_response(
|
|
574
|
+
self,
|
|
575
|
+
data: Any,
|
|
576
|
+
response_model: Optional[Type[T]] = None
|
|
577
|
+
) -> Union[T, Any]:
|
|
578
|
+
"""Parse response data into a Pydantic model if specified.
|
|
579
|
+
|
|
580
|
+
Args:
|
|
581
|
+
data: Raw response data (dict or list)
|
|
582
|
+
response_model: Optional Pydantic model type to parse into
|
|
583
|
+
|
|
584
|
+
Returns:
|
|
585
|
+
Parsed model instance(s) or raw AttrDict if no model specified
|
|
586
|
+
|
|
587
|
+
Raises:
|
|
588
|
+
WiilValidationError: When response validation fails
|
|
589
|
+
"""
|
|
590
|
+
if response_model is None:
|
|
591
|
+
return self._to_attr_obj(data)
|
|
592
|
+
|
|
593
|
+
try:
|
|
594
|
+
# Handle List[Model] types
|
|
595
|
+
origin = get_origin(response_model)
|
|
596
|
+
if origin is list:
|
|
597
|
+
args = get_args(response_model)
|
|
598
|
+
if args and isinstance(data, list):
|
|
599
|
+
item_model = args[0]
|
|
600
|
+
return [item_model.model_validate(item) for item in data]
|
|
601
|
+
|
|
602
|
+
# Handle single model
|
|
603
|
+
if isinstance(data, list):
|
|
604
|
+
return [response_model.model_validate(item) for item in data]
|
|
605
|
+
|
|
606
|
+
return response_model.model_validate(data)
|
|
607
|
+
|
|
608
|
+
except ValidationError as e:
|
|
609
|
+
raise WiilValidationError(
|
|
610
|
+
'Response validation failed',
|
|
611
|
+
details=e.errors()
|
|
612
|
+
)
|
|
613
|
+
|
|
509
614
|
def __del__(self):
|
|
510
615
|
"""Close the session when the client is destroyed."""
|
|
511
616
|
if hasattr(self, 'session'):
|
|
@@ -515,23 +620,11 @@ class HttpClient:
|
|
|
515
620
|
class AttrDict(dict):
|
|
516
621
|
"""Dictionary wrapper that allows attribute-style access."""
|
|
517
622
|
|
|
518
|
-
@staticmethod
|
|
519
|
-
def _snake_to_camel(value: str) -> str:
|
|
520
|
-
"""Convert snake_case names to camelCase for API payload lookups."""
|
|
521
|
-
if '_' not in value:
|
|
522
|
-
return value
|
|
523
|
-
parts = value.split('_')
|
|
524
|
-
return parts[0] + ''.join(part.capitalize() for part in parts[1:])
|
|
525
|
-
|
|
526
623
|
def __getattr__(self, key: str) -> Any:
|
|
527
|
-
|
|
624
|
+
try:
|
|
528
625
|
return self[key]
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
if camel_key in self:
|
|
532
|
-
return self[camel_key]
|
|
533
|
-
|
|
534
|
-
raise AttributeError(key)
|
|
626
|
+
except KeyError as exc:
|
|
627
|
+
raise AttributeError(key) from exc
|
|
535
628
|
|
|
536
629
|
|
|
537
630
|
__all__ = ['HttpClient']
|
|
@@ -101,57 +101,38 @@ class APIResponse(Generic[T]):
|
|
|
101
101
|
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
102
102
|
|
|
103
103
|
|
|
104
|
-
@dataclass
|
|
105
|
-
class APIErrorDetails:
|
|
106
|
-
"""Error details from an API error response.
|
|
107
|
-
|
|
108
|
-
Attributes:
|
|
109
|
-
code: Error code for programmatic handling
|
|
110
|
-
message: Human-readable error message
|
|
111
|
-
details: Additional error details (optional)
|
|
112
|
-
|
|
113
|
-
Example:
|
|
114
|
-
>>> error_details = APIErrorDetails(
|
|
115
|
-
... code='VALIDATION_ERROR',
|
|
116
|
-
... message='Invalid organization name',
|
|
117
|
-
... details={'field': 'companyName', 'issue': 'Must be at least 2 characters'}
|
|
118
|
-
... )
|
|
119
|
-
"""
|
|
120
|
-
|
|
121
|
-
code: str
|
|
122
|
-
message: str
|
|
123
|
-
details: Any = None
|
|
124
|
-
|
|
125
|
-
|
|
126
104
|
@dataclass
|
|
127
105
|
class APIErrorResponse:
|
|
128
106
|
"""Error response from the API.
|
|
129
107
|
|
|
130
108
|
This structure is returned when an API request fails (4xx or 5xx responses).
|
|
109
|
+
Matches the TypeScript SDK flat error structure.
|
|
131
110
|
|
|
132
111
|
Attributes:
|
|
133
112
|
success: Always False for error responses
|
|
134
|
-
|
|
135
|
-
|
|
113
|
+
status: HTTP status code
|
|
114
|
+
code: Error code for programmatic handling
|
|
115
|
+
message: Human-readable error message
|
|
116
|
+
meta: Additional error metadata (optional)
|
|
117
|
+
timestamp: ISO timestamp when the error occurred
|
|
136
118
|
|
|
137
119
|
Example:
|
|
138
120
|
>>> error_response = APIErrorResponse(
|
|
139
121
|
... success=False,
|
|
140
|
-
...
|
|
141
|
-
...
|
|
142
|
-
...
|
|
143
|
-
...
|
|
144
|
-
...
|
|
145
|
-
... 'issue': 'Must be at least 2 characters'
|
|
146
|
-
... }
|
|
147
|
-
... },
|
|
148
|
-
... metadata={'timestamp': 1704067200000, 'version': 'v1'}
|
|
122
|
+
... status=400,
|
|
123
|
+
... code='VALIDATION_ERROR',
|
|
124
|
+
... message='Invalid organization name',
|
|
125
|
+
... meta={'field': 'companyName', 'issue': 'Must be at least 2 characters'},
|
|
126
|
+
... timestamp='2024-01-01T00:00:00.000Z'
|
|
149
127
|
... )
|
|
150
128
|
"""
|
|
151
129
|
|
|
152
130
|
success: bool # Always False for error responses
|
|
153
|
-
|
|
154
|
-
|
|
131
|
+
status: int
|
|
132
|
+
code: str
|
|
133
|
+
message: str
|
|
134
|
+
timestamp: str
|
|
135
|
+
meta: Dict[str, Any] = field(default_factory=dict)
|
|
155
136
|
|
|
156
137
|
|
|
157
138
|
__all__ = [
|
|
@@ -159,5 +140,4 @@ __all__ = [
|
|
|
159
140
|
'APIResponse',
|
|
160
141
|
'APIResponseMetadata',
|
|
161
142
|
'APIErrorResponse',
|
|
162
|
-
'APIErrorDetails',
|
|
163
143
|
]
|
|
@@ -10,8 +10,6 @@ Example:
|
|
|
10
10
|
>>> print(org.company_name)
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
-
from typing import Any
|
|
14
|
-
|
|
15
13
|
from wiil.client.http_client import HttpClient
|
|
16
14
|
from wiil.models.account import Organization
|
|
17
15
|
|
|
@@ -55,8 +53,7 @@ class OrganizationsResource:
|
|
|
55
53
|
>>> print('Platform Email:', org.platform_email)
|
|
56
54
|
>>> print('Service Status:', org.service_status)
|
|
57
55
|
"""
|
|
58
|
-
|
|
59
|
-
return Organization.model_validate(response_data)
|
|
56
|
+
return self._http.get('/organizations', response_model=Organization)
|
|
60
57
|
|
|
61
58
|
|
|
62
59
|
__all__ = ['OrganizationsResource']
|
|
@@ -20,7 +20,7 @@ from wiil.models.account import (
|
|
|
20
20
|
CreateProject,
|
|
21
21
|
UpdateProject,
|
|
22
22
|
)
|
|
23
|
-
from wiil.types import PaginatedResult,
|
|
23
|
+
from wiil.types import PaginatedResult, PaginationRequest
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class ProjectsResource:
|
|
@@ -95,12 +95,12 @@ class ProjectsResource:
|
|
|
95
95
|
... ))
|
|
96
96
|
>>> print('Created project:', project.id)
|
|
97
97
|
"""
|
|
98
|
-
|
|
98
|
+
return self._http.post(
|
|
99
99
|
self._base_path,
|
|
100
100
|
data.model_dump(by_alias=True, exclude_none=True),
|
|
101
|
-
schema=CreateProject
|
|
101
|
+
schema=CreateProject,
|
|
102
|
+
response_model=Project
|
|
102
103
|
)
|
|
103
|
-
return Project.model_validate(response_data)
|
|
104
104
|
|
|
105
105
|
def get(self, project_id: str) -> Project:
|
|
106
106
|
"""Retrieve a project by ID.
|
|
@@ -120,8 +120,7 @@ class ProjectsResource:
|
|
|
120
120
|
>>> print('Project:', project.name)
|
|
121
121
|
>>> print('Is Default:', project.is_default)
|
|
122
122
|
"""
|
|
123
|
-
|
|
124
|
-
return Project.model_validate(response_data)
|
|
123
|
+
return self._http.get(f'{self._base_path}/{project_id}', response_model=Project)
|
|
125
124
|
|
|
126
125
|
def get_default(self) -> Project:
|
|
127
126
|
"""Retrieve the default project for the current organization.
|
|
@@ -141,8 +140,7 @@ class ProjectsResource:
|
|
|
141
140
|
>>> print('Default Project:', default_project.name)
|
|
142
141
|
>>> print('Project ID:', default_project.id)
|
|
143
142
|
"""
|
|
144
|
-
|
|
145
|
-
return Project.model_validate(response_data)
|
|
143
|
+
return self._http.get(f'{self._base_path}/default', response_model=Project)
|
|
146
144
|
|
|
147
145
|
def update(self, data: UpdateProject) -> Project:
|
|
148
146
|
"""Update an existing project.
|
|
@@ -170,12 +168,12 @@ class ProjectsResource:
|
|
|
170
168
|
... ))
|
|
171
169
|
>>> print('Updated project:', updated.name)
|
|
172
170
|
"""
|
|
173
|
-
|
|
171
|
+
return self._http.patch(
|
|
174
172
|
self._base_path,
|
|
175
173
|
data.model_dump(by_alias=True, exclude_none=True),
|
|
176
|
-
schema=UpdateProject
|
|
174
|
+
schema=UpdateProject,
|
|
175
|
+
response_model=Project
|
|
177
176
|
)
|
|
178
|
-
return Project.model_validate(response_data)
|
|
179
177
|
|
|
180
178
|
def delete(self, project_id: str) -> bool:
|
|
181
179
|
"""Delete a project.
|
|
@@ -241,10 +239,10 @@ class ProjectsResource:
|
|
|
241
239
|
query_params['sortDirection'] = params.sort_direction
|
|
242
240
|
|
|
243
241
|
query_string = f'?{urlencode(query_params)}' if query_params else ''
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
242
|
+
return self._http.get(
|
|
243
|
+
f'{self._base_path}{query_string}',
|
|
244
|
+
response_model=PaginatedResult[Project]
|
|
245
|
+
)
|
|
248
246
|
|
|
249
247
|
|
|
250
248
|
__all__ = ['ProjectsResource']
|