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.
Files changed (129) hide show
  1. {wiil_python-0.0.2 → wiil_python-0.0.3}/PKG-INFO +1 -1
  2. {wiil_python-0.0.2 → wiil_python-0.0.3}/pyproject.toml +1 -1
  3. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/client/http_client.py +156 -63
  4. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/client/types.py +16 -36
  5. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/account/organizations.py +1 -4
  6. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/account/projects.py +13 -15
  7. wiil_python-0.0.3/wiil/resources/business_mgt/business_services.py +145 -0
  8. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/business_mgt/customers.py +73 -8
  9. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/business_mgt/menu_orders.py +20 -7
  10. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/business_mgt/menus.py +167 -16
  11. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/business_mgt/product_orders.py +20 -7
  12. wiil_python-0.0.3/wiil/resources/business_mgt/products.py +280 -0
  13. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/business_mgt/property_config.py +215 -17
  14. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/business_mgt/property_inquiry.py +22 -7
  15. wiil_python-0.0.3/wiil/resources/business_mgt/reservation_resources.py +147 -0
  16. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/business_mgt/reservations.py +35 -11
  17. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/business_mgt/service_appointments.py +27 -8
  18. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/agent_configs.py +9 -4
  19. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/conversation_configs.py +8 -2
  20. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/deployment_channels.py +17 -6
  21. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/deployment_configs.py +22 -8
  22. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/dynamic_agent_status.py +4 -1
  23. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/dynamic_phone_agent.py +12 -4
  24. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/dynamic_web_agent.py +12 -4
  25. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/instruction_configs.py +13 -5
  26. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/knowledge_sources.py +5 -2
  27. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/phone_configs.py +15 -5
  28. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/provisioning_configs.py +20 -6
  29. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/support_models.py +14 -12
  30. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/telephony_provider.py +13 -3
  31. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/translation_sessions.py +8 -2
  32. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/services/translation/service.py +1 -1
  33. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil_python.egg-info/PKG-INFO +1 -1
  34. wiil_python-0.0.2/wiil/resources/business_mgt/business_services.py +0 -80
  35. wiil_python-0.0.2/wiil/resources/business_mgt/products.py +0 -142
  36. wiil_python-0.0.2/wiil/resources/business_mgt/reservation_resources.py +0 -76
  37. {wiil_python-0.0.2 → wiil_python-0.0.3}/README.md +0 -0
  38. {wiil_python-0.0.2 → wiil_python-0.0.3}/setup.cfg +0 -0
  39. {wiil_python-0.0.2 → wiil_python-0.0.3}/setup.py +0 -0
  40. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/__init__.py +0 -0
  41. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/client/__init__.py +0 -0
  42. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/client/async_wiil_client.py +0 -0
  43. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/client/wiil_client.py +0 -0
  44. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/client/will_service.py +0 -0
  45. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/errors/__init__.py +0 -0
  46. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/errors/exceptions.py +0 -0
  47. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/__init__.py +0 -0
  48. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/account/__init__.py +0 -0
  49. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/account/organization.py +0 -0
  50. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/account/project.py +0 -0
  51. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/account/supported_business_verticals.py +0 -0
  52. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/assistant_setups/__init__.py +0 -0
  53. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/assistant_setups/assistant_setup_result.py +0 -0
  54. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/assistant_setups/base_assistant_setup.py +0 -0
  55. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/assistant_setups/phone_assistant_setup.py +0 -0
  56. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/assistant_setups/web_assistant_setup.py +0 -0
  57. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/base.py +0 -0
  58. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/__init__.py +0 -0
  59. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/appointment_additional_info.py +0 -0
  60. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/appointment_field_config.py +0 -0
  61. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/customer.py +0 -0
  62. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/menu_config.py +0 -0
  63. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/menu_order.py +0 -0
  64. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/order.py +0 -0
  65. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/product_config.py +0 -0
  66. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/product_order.py +0 -0
  67. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/property_config.py +0 -0
  68. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/property_inquiry.py +0 -0
  69. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/reservation.py +0 -0
  70. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/reservation_resource.py +0 -0
  71. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/service_appointment.py +0 -0
  72. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/service_config.py +0 -0
  73. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/business_mgt/service_person.py +0 -0
  74. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/conversation/__init__.py +0 -0
  75. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/conversation/conversation_config.py +0 -0
  76. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/conversation/conversation_message.py +0 -0
  77. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/conversation/translation_config.py +0 -0
  78. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/conversation/translation_conversation.py +0 -0
  79. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/request/__init__.py +0 -0
  80. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/request/paginated_query.py +0 -0
  81. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/request/paginated_result.py +0 -0
  82. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/__init__.py +0 -0
  83. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/agent_config.py +0 -0
  84. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/call_transfer_config.py +0 -0
  85. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/deployment_config.py +0 -0
  86. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/dynamic_setup/__init__.py +0 -0
  87. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/dynamic_setup/base_agent_setup.py +0 -0
  88. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/dynamic_setup/phone_agent_setup.py +0 -0
  89. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/dynamic_setup/web_agent_setup.py +0 -0
  90. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/instruction_config.py +0 -0
  91. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/interaction_channels.py +0 -0
  92. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/knowledge.py +0 -0
  93. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/phone_config.py +0 -0
  94. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/phone_number.py +0 -0
  95. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/provisioning_config.py +0 -0
  96. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/support_llm.py +0 -0
  97. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/service_mgt/voice_language.py +0 -0
  98. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/type_definitions/__init__.py +0 -0
  99. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/type_definitions/account_definitions.py +0 -0
  100. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/type_definitions/business_definitions.py +0 -0
  101. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/type_definitions/conversation_definitions.py +0 -0
  102. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/type_definitions/dynamic_fields/__init__.py +0 -0
  103. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/type_definitions/dynamic_fields/field_definition.py +0 -0
  104. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/type_definitions/dynamic_fields/field_types.py +0 -0
  105. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/type_definitions/knowledge_definitions.py +0 -0
  106. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/models/type_definitions/service_config_definitions.py +0 -0
  107. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/py.typed +0 -0
  108. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/__init__.py +0 -0
  109. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/account/__init__.py +0 -0
  110. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/business_mgt/__init__.py +0 -0
  111. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/resources/service_mgt/__init__.py +0 -0
  112. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/services/__init__.py +0 -0
  113. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/services/ott/__init__.py +0 -0
  114. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/services/ott/models.py +0 -0
  115. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/services/ott/service.py +0 -0
  116. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/services/translation/__init__.py +0 -0
  117. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/services/translation/models.py +0 -0
  118. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/types/__init__.py +0 -0
  119. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/types/account_types.py +0 -0
  120. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/types/business_types.py +0 -0
  121. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/types/conversation_types.py +0 -0
  122. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/types/knowledge_types.py +0 -0
  123. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/types/paginated_quest.py +0 -0
  124. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/types/paginated_result.py +0 -0
  125. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil/types/service_types.py +0 -0
  126. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil_python.egg-info/SOURCES.txt +0 -0
  127. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil_python.egg-info/dependency_links.txt +0 -0
  128. {wiil_python-0.0.2 → wiil_python-0.0.3}/wiil_python.egg-info/requires.txt +0 -0
  129. {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.2
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.2"
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
- 'Content-Type': 'application/json',
70
- 'X-WIIL-API-Key': self.api_key,
69
+ 'X-Wiil-Api-Key': self.api_key,
71
70
  })
72
71
 
73
- def get(self, path: str, **kwargs: Any) -> Any:
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 extracted from the APIResponse wrapper
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
- >>> data = http.get('/organizations')
89
- >>> print(data['id'])
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
- # Return the data field from the APIResponse wrapper
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._to_attr_obj(response_data['data'])
117
+ return self._parse_response(response_data['data'], response_model)
107
118
 
108
- return self._to_attr_obj(response_data)
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
- ) -> Any:
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 extracted from the APIResponse wrapper
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 pydantic import BaseModel
158
- >>> class CreateOrgRequest(BaseModel):
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=CreateOrgRequest
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
- # Return the data field from the APIResponse wrapper
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._to_attr_obj(response_data['data'])
212
+ return self._parse_response(response_data['data'], response_model)
197
213
 
198
- return self._to_attr_obj(response_data)
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
- ) -> Any:
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 extracted from the APIResponse wrapper
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
- >>> data = http.put(
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
- # Return the data field from the APIResponse wrapper
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._to_attr_obj(response_data['data'])
306
+ return self._parse_response(response_data['data'], response_model)
283
307
 
284
- return self._to_attr_obj(response_data)
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
- ) -> Any:
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 extracted from the APIResponse wrapper
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
- >>> data = http.patch(
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
- # Return the data field from the APIResponse wrapper
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._to_attr_obj(response_data['data'])
400
+ return self._parse_response(response_data['data'], response_model)
369
401
 
370
- return self._to_attr_obj(response_data)
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(self, path: str, **kwargs: Any) -> Any:
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 extracted from the APIResponse wrapper (may be None)
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
- # Return the data field from the APIResponse wrapper
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._to_attr_obj(response_data['data'])
471
+ return self._parse_response(response_data['data'], response_model)
429
472
 
430
- return self._to_attr_obj(response_data)
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=error_info.get('message', f'Request failed with status {status_code}'),
484
- status_code=status_code,
485
- code=error_info.get('code', 'UNKNOWN_ERROR'),
486
- details=error_info.get('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
- if key in self:
624
+ try:
528
625
  return self[key]
529
-
530
- camel_key = self._snake_to_camel(key)
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
- error: Error details containing code, message, and optional details
135
- metadata: Response metadata
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
- ... error={
141
- ... 'code': 'VALIDATION_ERROR',
142
- ... 'message': 'Invalid organization name',
143
- ... 'details': {
144
- ... 'field': 'companyName',
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
- error: Dict[str, Any]
154
- metadata: Dict[str, Any] = field(default_factory=dict)
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
- response_data: Any = self._http.get('/organizations')
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, PaginationMeta, PaginationRequest
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
- response_data = self._http.post(
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
- response_data = self._http.get(f'{self._base_path}/{project_id}')
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
- response_data = self._http.get(f'{self._base_path}/default')
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
- response_data = self._http.patch(
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
- response_data = self._http.get(f'{self._base_path}{query_string}')
245
- items = [Project.model_validate(item) for item in response_data.get('data', [])]
246
- meta = PaginationMeta.model_validate(response_data.get('meta', {}))
247
- return PaginatedResult[Project](data=items, meta=meta)
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']