payi 0.1.0a34__py3-none-any.whl → 0.1.0a36__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of payi might be problematic. Click here for more details.

Files changed (36) hide show
  1. payi/_base_client.py +6 -0
  2. payi/_models.py +9 -5
  3. payi/_response.py +9 -3
  4. payi/_version.py +1 -1
  5. payi/lib/AnthropicInstrumentor.py +97 -0
  6. payi/lib/Instruments.py +7 -0
  7. payi/lib/OpenAIInstrumentor.py +89 -0
  8. payi/lib/Stopwatch.py +27 -0
  9. payi/lib/helpers.py +14 -9
  10. payi/lib/instrument.py +514 -0
  11. payi/resources/billing_models.py +2 -2
  12. payi/resources/categories/categories.py +2 -2
  13. payi/resources/categories/resources.py +2 -2
  14. payi/resources/experiences/__init__.py +0 -14
  15. payi/resources/experiences/experiences.py +2 -34
  16. payi/resources/experiences/properties.py +4 -4
  17. payi/resources/experiences/types.py +2 -2
  18. payi/resources/ingest.py +45 -47
  19. payi/resources/limits/limits.py +2 -2
  20. payi/resources/limits/tags.py +2 -2
  21. payi/resources/price_modifiers.py +2 -2
  22. payi/resources/requests/properties.py +4 -4
  23. payi/resources/requests/requests.py +2 -2
  24. payi/types/experiences/__init__.py +0 -2
  25. payi/types/ingest_bulk_params.py +1 -1
  26. payi/types/ingest_event_param.py +18 -8
  27. payi/types/ingest_units_params.py +19 -8
  28. payi/types/limit_history_response.py +1 -1
  29. payi/types/shared/evaluation_response.py +0 -1
  30. {payi-0.1.0a34.dist-info → payi-0.1.0a36.dist-info}/METADATA +12 -12
  31. {payi-0.1.0a34.dist-info → payi-0.1.0a36.dist-info}/RECORD +33 -31
  32. payi/resources/experiences/csat.py +0 -188
  33. payi/types/experiences/csat_create_params.py +0 -14
  34. payi/types/experiences/csat_response.py +0 -10
  35. {payi-0.1.0a34.dist-info → payi-0.1.0a36.dist-info}/WHEEL +0 -0
  36. {payi-0.1.0a34.dist-info → payi-0.1.0a36.dist-info}/licenses/LICENSE +0 -0
payi/lib/instrument.py ADDED
@@ -0,0 +1,514 @@
1
+ import json
2
+ import uuid
3
+ import asyncio
4
+ import inspect
5
+ import logging
6
+ import traceback
7
+ from typing import Any, Set, Union, Callable, Optional
8
+
9
+ from wrapt import ObjectProxy # type: ignore
10
+
11
+ from payi import Payi, AsyncPayi
12
+ from payi.types import IngestUnitsParams
13
+
14
+ from .Stopwatch import Stopwatch
15
+ from .Instruments import Instruments
16
+
17
+
18
+ class PayiInstrumentor:
19
+ def __init__(
20
+ self,
21
+ payi: Union[Payi, AsyncPayi, None] = None,
22
+ instruments: Union[Set[Instruments], None] = None,
23
+ log_prompt_and_response: bool = True,
24
+ prompt_and_response_logger: Optional[
25
+ Callable[[str, "dict[str, str]"], None]
26
+ ] = None, # (request id, dict of data to store) -> None
27
+ ):
28
+ self._payi: Union[Payi, AsyncPayi, None] = payi
29
+ self._context_stack: list[dict[str, Any]] = [] # Stack of context dictionaries
30
+ self._log_prompt_and_response: bool = log_prompt_and_response
31
+ self._prompt_and_response_logger: Optional[Callable[[str, dict[str, str]], None]] = prompt_and_response_logger
32
+
33
+ self._blocked_limits: set[str] = set()
34
+ self._exceeded_limits: set[str] = set()
35
+
36
+ if instruments is None or Instruments.ALL in instruments:
37
+ self._instrument_all()
38
+ else:
39
+ self._instrument_specific(instruments)
40
+
41
+ def _instrument_all(self) -> None:
42
+ self._instrument_openai()
43
+ self._instrument_anthropic()
44
+
45
+ def _instrument_specific(self, instruments: Set[Instruments]) -> None:
46
+ if Instruments.OPENAI in instruments:
47
+ self._instrument_openai()
48
+ if Instruments.ANTHROPIC in instruments:
49
+ self._instrument_anthropic()
50
+
51
+ def _instrument_openai(self) -> None:
52
+ from .OpenAIInstrumentor import OpenAiInstrumentor
53
+
54
+ try:
55
+ OpenAiInstrumentor.instrument(self)
56
+
57
+ except Exception as e:
58
+ logging.error(f"Error instrumenting OpenAI: {e}")
59
+
60
+ def _instrument_anthropic(self) -> None:
61
+ from .AnthropicInstrumentor import AnthropicIntrumentor
62
+
63
+ try:
64
+ AnthropicIntrumentor.instrument(self)
65
+
66
+ except Exception as e:
67
+ logging.error(f"Error instrumenting Anthropic: {e}")
68
+
69
+ def _ingest_units(self, ingest_units: IngestUnitsParams) -> None:
70
+ # return early if there are no units to ingest and on a successul ingest request
71
+ if int(ingest_units.get("http_status_code") or 0) < 400:
72
+ units = ingest_units.get("units", {})
73
+ if not units or all(unit.get("input", 0) == 0 and unit.get("output", 0) == 0 for unit in units.values()):
74
+ logging.error(
75
+ 'No units to ingest. For OpenAI streaming calls, make sure you pass stream_options={"include_usage": True}'
76
+ )
77
+ return
78
+
79
+ try:
80
+ if isinstance(self._payi, AsyncPayi):
81
+ loop = asyncio.new_event_loop()
82
+ asyncio.set_event_loop(loop)
83
+ try:
84
+ ingest_result = loop.run_until_complete(self._payi.ingest.units(**ingest_units))
85
+ finally:
86
+ loop.close()
87
+ elif isinstance(self._payi, Payi):
88
+ ingest_result = self._payi.ingest.units(**ingest_units)
89
+ else:
90
+ logging.error("No payi instance to ingest units")
91
+ return
92
+
93
+ if ingest_result.xproxy_result.limits:
94
+ for limit_id, state in ingest_result.xproxy_result.limits.items():
95
+ removeBlockedId: bool = False
96
+
97
+ if state.state == "blocked":
98
+ self._blocked_limits.add(limit_id)
99
+ elif state.state == "exceeded":
100
+ self._exceeded_limits.add(limit_id)
101
+ removeBlockedId = True
102
+ elif state.state == "ok":
103
+ removeBlockedId = True
104
+
105
+ # opportunistically remove blocked limits
106
+ if removeBlockedId:
107
+ self._blocked_limits.discard(limit_id)
108
+
109
+ if self._log_prompt_and_response and self._prompt_and_response_logger:
110
+ request_id = ingest_result.xproxy_result.request_id
111
+
112
+ log_data = {}
113
+ response_json = ingest_units.pop("provider_response_json", None)
114
+ request_json = ingest_units.pop("provider_request_json", None)
115
+ stack_trace = ingest_units.get("properties", {}).pop("system.stack_trace", None) # type: ignore
116
+
117
+ if response_json is not None:
118
+ # response_json is a list of strings, convert a single json string
119
+ log_data["provider_response_json"] = json.dumps(response_json)
120
+ if request_json is not None:
121
+ log_data["provider_request_json"] = request_json
122
+ if stack_trace is not None:
123
+ log_data["stack_trace"] = stack_trace
124
+
125
+ self._prompt_and_response_logger(request_id, log_data) # type: ignore
126
+
127
+ except Exception as e:
128
+ logging.error(f"Error Pay-i ingesting result: {e}")
129
+
130
+ def _call_func(
131
+ self,
132
+ func: Any,
133
+ proxy: bool,
134
+ limit_ids: Optional["list[str]"],
135
+ request_tags: Optional["list[str]"],
136
+ experience_name: Optional[str],
137
+ experience_id: Optional[str],
138
+ user_id: Optional[str],
139
+ *args: Any,
140
+ **kwargs: Any,
141
+ ) -> Any:
142
+ if len(self._context_stack) > 0:
143
+ # copy current context into the upcoming context
144
+ context = self._context_stack[-1].copy()
145
+ context.pop("proxy", None)
146
+ previous_experience_name = context["experience_name"]
147
+ previous_experience_id = context["experience_id"]
148
+ else:
149
+ context = {}
150
+ previous_experience_name = None
151
+ previous_experience_id = None
152
+
153
+ with self:
154
+ context["proxy"] = proxy
155
+
156
+ # Handle experience name and ID logic
157
+ if not experience_name:
158
+ # If no experience_name specified, use previous values
159
+ context["experience_name"] = previous_experience_name
160
+ context["experience_id"] = previous_experience_id
161
+ else:
162
+ # If experience_name is specified
163
+ if experience_name == previous_experience_name:
164
+ # Same experience name, use previous ID unless new one specified
165
+ context["experience_name"] = experience_name
166
+ context["experience_id"] = experience_id if experience_id else previous_experience_id
167
+ else:
168
+ # Different experience name, use specified ID or generate one
169
+ context["experience_name"] = experience_name
170
+ context["experience_id"] = experience_id if experience_id else str(uuid.uuid4())
171
+
172
+ # set any values explicitly passed by the caller, otherwise use what is already in the context
173
+ if limit_ids:
174
+ context["limit_ids"] = limit_ids
175
+ if request_tags:
176
+ context["request_tags"] = request_tags
177
+ if user_id:
178
+ context["user_id"] = user_id
179
+
180
+ self.set_context(context)
181
+
182
+ return func(*args, **kwargs)
183
+
184
+ def __enter__(self) -> Any:
185
+ # Push a new context dictionary onto the stack
186
+ self._context_stack.append({})
187
+ return self
188
+
189
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
190
+ # Pop the current context off the stack
191
+ if self._context_stack:
192
+ self._context_stack.pop()
193
+
194
+ def set_context(self, context: "dict[str, Any]") -> None:
195
+ # Update the current top of the stack with the provided context
196
+ if self._context_stack:
197
+ self._context_stack[-1].update(context)
198
+
199
+ def get_context(self) -> Optional["dict[str, Any]"]:
200
+ # Return the current top of the stack
201
+ return self._context_stack[-1] if self._context_stack else None
202
+
203
+ def chat_wrapper(
204
+ self,
205
+ category: str,
206
+ process_chunk: Callable[[Any, IngestUnitsParams], None],
207
+ process_synchronous_response: Optional[Callable[[Any, IngestUnitsParams, bool], None]],
208
+ wrapped: Any,
209
+ instance: Any,
210
+ args: Any,
211
+ kwargs: Any,
212
+ ) -> Any:
213
+ context = self.get_context()
214
+
215
+ if not context:
216
+ # should not happen
217
+ return wrapped(*args, **kwargs)
218
+
219
+ if context.get("proxy", True):
220
+ proxy_extra_headers = kwargs.get("extra_headers", {})
221
+
222
+ self._update_headers(context, proxy_extra_headers)
223
+
224
+ if "extra_headers" not in kwargs:
225
+ kwargs["extra_headers"] = proxy_extra_headers
226
+
227
+ return wrapped(*args, **kwargs)
228
+
229
+ ingest: IngestUnitsParams = {"category": category, "resource": kwargs.get("model"), "units": {}}
230
+
231
+ # blocked_limit = next((limit for limit in (context.get('limit_ids') or []) if limit in self._blocked_limits), None)
232
+ # if blocked_limit:
233
+ # raise Exception(f"Limit {blocked_limit} is blocked")
234
+ current_frame = inspect.currentframe()
235
+ # f_back excludes the current frame, strip() cleans up whitespace and newlines
236
+ stack = [frame.strip() for frame in traceback.format_stack(current_frame.f_back)] # type: ignore
237
+
238
+ # TODO add back once feature is in prod
239
+ # ingest['properties'] = { 'system.stack_trace': json.dumps(stack) }
240
+
241
+ sw = Stopwatch()
242
+ stream = kwargs.get("stream", False)
243
+
244
+ try:
245
+ limit_ids = context.get("limit_ids")
246
+ request_tags = context.get("request_tags")
247
+ experience_name = context.get("experience_name")
248
+ experience_id = context.get("experience_id")
249
+ user_id = context.get("user_id")
250
+
251
+ if limit_ids:
252
+ ingest["limit_ids"] = limit_ids
253
+ if request_tags:
254
+ ingest["request_tags"] = request_tags
255
+ if experience_name:
256
+ ingest["experience_name"] = experience_name
257
+ if experience_id:
258
+ ingest["experience_id"] = experience_id
259
+ if user_id:
260
+ ingest["user_id"] = user_id
261
+
262
+ extra_headers: dict[str, str] = kwargs.get("extra_headers") or {}
263
+
264
+ if len(extra_headers) > 0:
265
+ ingest["provider_request_headers"] = {k: [v] for k, v in extra_headers.items()} # type: ignore
266
+
267
+ provider_prompt = {}
268
+ for k, v in kwargs.items():
269
+ if k == "messages":
270
+ provider_prompt[k] = [m.model_dump() if hasattr(m, "model_dump") else m for m in v]
271
+ elif k in ["extra_headers", "extra_query"]:
272
+ pass
273
+ else:
274
+ provider_prompt[k] = v
275
+
276
+ if self._log_prompt_and_response:
277
+ ingest["provider_request_json"] = json.dumps(provider_prompt)
278
+
279
+ sw.start()
280
+ response = wrapped(*args, **kwargs.copy())
281
+
282
+ except Exception as e: # pylint: disable=broad-except
283
+ sw.stop()
284
+ duration = sw.elapsed_ms_int()
285
+
286
+ # TODO ingest error
287
+
288
+ raise e
289
+
290
+ if stream:
291
+ return ChatStreamWrapper(
292
+ response=response,
293
+ instance=instance,
294
+ instrumentor=self,
295
+ log_prompt_and_response=self._log_prompt_and_response,
296
+ ingest=ingest,
297
+ stopwatch=sw,
298
+ process_chunk=process_chunk,
299
+ )
300
+
301
+ sw.stop()
302
+ duration = sw.elapsed_ms_int()
303
+ ingest["end_to_end_latency_ms"] = duration
304
+ ingest["http_status_code"] = 200
305
+
306
+ if process_synchronous_response:
307
+ process_synchronous_response(response, ingest, self._log_prompt_and_response)
308
+
309
+ self._ingest_units(ingest)
310
+
311
+ return response
312
+
313
+ @staticmethod
314
+ def _update_headers(
315
+ context: "dict[str, Any]",
316
+ extra_headers: "dict[str, str]",
317
+ ) -> None:
318
+ limit_ids: Optional[list[str]] = context.get("limit_ids")
319
+ request_tags: Optional[list[str]] = context.get("request_tags")
320
+ experience_name: Optional[str] = context.get("experience_name")
321
+ experience_id: Optional[str] = context.get("experience_id")
322
+ user_id: Optional[str] = context.get("user_id")
323
+
324
+ if limit_ids is not None:
325
+ existing_limit_ids = extra_headers.get("xProxy-Limit-IDs")
326
+ limit_ids_str = ",".join(limit_ids)
327
+ if existing_limit_ids is None:
328
+ extra_headers["xProxy-Limit-IDs"] = limit_ids_str
329
+ else:
330
+ extra_headers["xProxy-Limit-IDs"] = f"{existing_limit_ids},{limit_ids_str}"
331
+
332
+ if request_tags is not None:
333
+ existing_request_tags = extra_headers.get("xProxy-Request-Tags")
334
+ request_tags_str = ",".join(request_tags)
335
+ if existing_request_tags is None:
336
+ extra_headers["xProxy-Request-Tags"] = request_tags_str
337
+ else:
338
+ extra_headers["xProxy-Request-Tags"] = f"{existing_request_tags},{request_tags_str}"
339
+
340
+ if experience_name is not None:
341
+ extra_headers["xProxy-Experience-Name"] = experience_name
342
+
343
+ if experience_id is not None:
344
+ extra_headers["xProxy-Experience-ID"] = experience_id
345
+
346
+ if user_id is not None:
347
+ extra_headers["xProxy-User-ID"] = user_id
348
+
349
+ @staticmethod
350
+ def payi_wrapper(func: Any) -> Any:
351
+ def _payi_wrapper(o: Any) -> Any:
352
+ def wrapper(wrapped: Any, instance: Any, args: Any, kwargs: Any) -> Any:
353
+ return func(
354
+ o,
355
+ wrapped,
356
+ instance,
357
+ args,
358
+ kwargs,
359
+ )
360
+
361
+ return wrapper
362
+
363
+ return _payi_wrapper
364
+
365
+
366
+ class ChatStreamWrapper(ObjectProxy): # type: ignore
367
+ def __init__(
368
+ self,
369
+ response: Any,
370
+ instance: Any,
371
+ instrumentor: PayiInstrumentor,
372
+ ingest: IngestUnitsParams,
373
+ stopwatch: Stopwatch,
374
+ process_chunk: Optional[Callable[[Any, IngestUnitsParams], None]] = None,
375
+ log_prompt_and_response: bool = True,
376
+ ) -> None:
377
+ super().__init__(response) # type: ignore
378
+
379
+ self._response = response
380
+ self._instance = instance
381
+
382
+ self._instrumentor = instrumentor
383
+ self._stopwatch: Stopwatch = stopwatch
384
+ self._ingest: IngestUnitsParams = ingest
385
+ self._log_prompt_and_response: bool = log_prompt_and_response
386
+ self._responses: list[str] = []
387
+
388
+ self._process_chunk: Optional[Callable[[Any, IngestUnitsParams], None]] = process_chunk
389
+
390
+ self._first_token: bool = True
391
+
392
+ def __enter__(self) -> Any:
393
+ return self
394
+
395
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
396
+ self.__wrapped__.__exit__(exc_type, exc_val, exc_tb) # type: ignore
397
+
398
+ async def __aenter__(self) -> Any:
399
+ return self
400
+
401
+ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
402
+ await self.__wrapped__.__aexit__(exc_type, exc_val, exc_tb) # type: ignore
403
+
404
+ def __iter__(self) -> Any:
405
+ return self
406
+
407
+ def __aiter__(self) -> Any:
408
+ return self
409
+
410
+ def __next__(self) -> Any:
411
+ try:
412
+ chunk: Any = self.__wrapped__.__next__() # type: ignore
413
+ except Exception as e:
414
+ if isinstance(e, StopIteration):
415
+ self._stop_iteration()
416
+ raise e
417
+ else:
418
+ self._evaluate_chunk(chunk)
419
+ return chunk
420
+
421
+ async def __anext__(self) -> Any:
422
+ try:
423
+ chunk: Any = await self.__wrapped__.__anext__() # type: ignore
424
+ except Exception as e:
425
+ if isinstance(e, StopAsyncIteration):
426
+ self._stop_iteration()
427
+ raise e
428
+ else:
429
+ self._evaluate_chunk(chunk)
430
+ return chunk
431
+
432
+ def _evaluate_chunk(self, chunk: Any) -> None:
433
+ if self._first_token:
434
+ self._ingest["time_to_first_token_ms"] = self._stopwatch.elapsed_ms_int()
435
+ self._first_token = False
436
+
437
+ if self._log_prompt_and_response:
438
+ self._responses.append(chunk.to_json())
439
+
440
+ if self._process_chunk:
441
+ self._process_chunk(chunk, self._ingest)
442
+
443
+ def _stop_iteration(self) -> None:
444
+ self._stopwatch.stop()
445
+ self._ingest["end_to_end_latency_ms"] = self._stopwatch.elapsed_ms_int()
446
+ self._ingest["http_status_code"] = 200
447
+
448
+ if self._log_prompt_and_response:
449
+ self._ingest["provider_response_json"] = self._responses
450
+
451
+ self._instrumentor._ingest_units(self._ingest)
452
+
453
+
454
+ global _instrumentor
455
+ _instrumentor: PayiInstrumentor
456
+
457
+
458
+ def payi_instrument(
459
+ payi: Optional[Union[Payi, AsyncPayi]] = None,
460
+ instruments: Optional[Set[Instruments]] = None,
461
+ log_prompt_and_response: bool = True,
462
+ prompt_and_response_logger: Optional[Callable[[str, "dict[str, str]"], None]] = None,
463
+ ) -> None:
464
+ global _instrumentor
465
+ _instrumentor = PayiInstrumentor(
466
+ payi=payi,
467
+ instruments=instruments,
468
+ log_prompt_and_response=log_prompt_and_response,
469
+ prompt_and_response_logger=prompt_and_response_logger,
470
+ )
471
+
472
+
473
+ def ingest(
474
+ limit_ids: Optional["list[str]"] = None,
475
+ request_tags: Optional["list[str]"] = None,
476
+ experience_name: Optional[str] = None,
477
+ experience_id: Optional[str] = None,
478
+ user_id: Optional[str] = None,
479
+ ) -> Any:
480
+ def _ingest(func: Any) -> Any:
481
+ def _ingest_wrapper(*args: Any, **kwargs: Any) -> Any:
482
+ return _instrumentor._call_func(
483
+ func,
484
+ False, # false -> ingest
485
+ limit_ids,
486
+ request_tags,
487
+ experience_name,
488
+ experience_id,
489
+ user_id,
490
+ *args,
491
+ **kwargs,
492
+ )
493
+
494
+ return _ingest_wrapper
495
+
496
+ return _ingest
497
+
498
+
499
+ def proxy(
500
+ limit_ids: Optional["list[str]"] = None,
501
+ request_tags: Optional["list[str]"] = None,
502
+ experience_name: Optional[str] = None,
503
+ experience_id: Optional[str] = None,
504
+ user_id: Optional[str] = None,
505
+ ) -> Any:
506
+ def _proxy(func: Any) -> Any:
507
+ def _proxy_wrapper(*args: Any, **kwargs: Any) -> Any:
508
+ return _instrumentor._call_func(
509
+ func, True, limit_ids, request_tags, experience_name, experience_id, user_id, *args, **kwargs
510
+ )
511
+
512
+ return _proxy_wrapper
513
+
514
+ return _proxy
@@ -32,7 +32,7 @@ class BillingModelsResource(SyncAPIResource):
32
32
  @cached_property
33
33
  def with_raw_response(self) -> BillingModelsResourceWithRawResponse:
34
34
  """
35
- This property can be used as a prefix for any HTTP method call to return the
35
+ This property can be used as a prefix for any HTTP method call to return
36
36
  the raw response object instead of the parsed content.
37
37
 
38
38
  For more information, see https://www.github.com/Pay-i/pay-i-python#accessing-raw-response-data-eg-headers
@@ -226,7 +226,7 @@ class AsyncBillingModelsResource(AsyncAPIResource):
226
226
  @cached_property
227
227
  def with_raw_response(self) -> AsyncBillingModelsResourceWithRawResponse:
228
228
  """
229
- This property can be used as a prefix for any HTTP method call to return the
229
+ This property can be used as a prefix for any HTTP method call to return
230
230
  the raw response object instead of the parsed content.
231
231
 
232
232
  For more information, see https://www.github.com/Pay-i/pay-i-python#accessing-raw-response-data-eg-headers
@@ -38,7 +38,7 @@ class CategoriesResource(SyncAPIResource):
38
38
  @cached_property
39
39
  def with_raw_response(self) -> CategoriesResourceWithRawResponse:
40
40
  """
41
- This property can be used as a prefix for any HTTP method call to return the
41
+ This property can be used as a prefix for any HTTP method call to return
42
42
  the raw response object instead of the parsed content.
43
43
 
44
44
  For more information, see https://www.github.com/Pay-i/pay-i-python#accessing-raw-response-data-eg-headers
@@ -184,7 +184,7 @@ class AsyncCategoriesResource(AsyncAPIResource):
184
184
  @cached_property
185
185
  def with_raw_response(self) -> AsyncCategoriesResourceWithRawResponse:
186
186
  """
187
- This property can be used as a prefix for any HTTP method call to return the
187
+ This property can be used as a prefix for any HTTP method call to return
188
188
  the raw response object instead of the parsed content.
189
189
 
190
190
  For more information, see https://www.github.com/Pay-i/pay-i-python#accessing-raw-response-data-eg-headers
@@ -32,7 +32,7 @@ class ResourcesResource(SyncAPIResource):
32
32
  @cached_property
33
33
  def with_raw_response(self) -> ResourcesResourceWithRawResponse:
34
34
  """
35
- This property can be used as a prefix for any HTTP method call to return the
35
+ This property can be used as a prefix for any HTTP method call to return
36
36
  the raw response object instead of the parsed content.
37
37
 
38
38
  For more information, see https://www.github.com/Pay-i/pay-i-python#accessing-raw-response-data-eg-headers
@@ -218,7 +218,7 @@ class AsyncResourcesResource(AsyncAPIResource):
218
218
  @cached_property
219
219
  def with_raw_response(self) -> AsyncResourcesResourceWithRawResponse:
220
220
  """
221
- This property can be used as a prefix for any HTTP method call to return the
221
+ This property can be used as a prefix for any HTTP method call to return
222
222
  the raw response object instead of the parsed content.
223
223
 
224
224
  For more information, see https://www.github.com/Pay-i/pay-i-python#accessing-raw-response-data-eg-headers
@@ -1,13 +1,5 @@
1
1
  # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
2
 
3
- from .csat import (
4
- CsatResource,
5
- AsyncCsatResource,
6
- CsatResourceWithRawResponse,
7
- AsyncCsatResourceWithRawResponse,
8
- CsatResourceWithStreamingResponse,
9
- AsyncCsatResourceWithStreamingResponse,
10
- )
11
3
  from .types import (
12
4
  TypesResource,
13
5
  AsyncTypesResource,
@@ -40,12 +32,6 @@ __all__ = [
40
32
  "AsyncTypesResourceWithRawResponse",
41
33
  "TypesResourceWithStreamingResponse",
42
34
  "AsyncTypesResourceWithStreamingResponse",
43
- "CsatResource",
44
- "AsyncCsatResource",
45
- "CsatResourceWithRawResponse",
46
- "AsyncCsatResourceWithRawResponse",
47
- "CsatResourceWithStreamingResponse",
48
- "AsyncCsatResourceWithStreamingResponse",
49
35
  "PropertiesResource",
50
36
  "AsyncPropertiesResource",
51
37
  "PropertiesResourceWithRawResponse",
@@ -4,14 +4,6 @@ from __future__ import annotations
4
4
 
5
5
  import httpx
6
6
 
7
- from .csat import (
8
- CsatResource,
9
- AsyncCsatResource,
10
- CsatResourceWithRawResponse,
11
- AsyncCsatResourceWithRawResponse,
12
- CsatResourceWithStreamingResponse,
13
- AsyncCsatResourceWithStreamingResponse,
14
- )
15
7
  from .types import (
16
8
  TypesResource,
17
9
  AsyncTypesResource,
@@ -48,10 +40,6 @@ class ExperiencesResource(SyncAPIResource):
48
40
  def types(self) -> TypesResource:
49
41
  return TypesResource(self._client)
50
42
 
51
- @cached_property
52
- def csat(self) -> CsatResource:
53
- return CsatResource(self._client)
54
-
55
43
  @cached_property
56
44
  def properties(self) -> PropertiesResource:
57
45
  return PropertiesResource(self._client)
@@ -59,7 +47,7 @@ class ExperiencesResource(SyncAPIResource):
59
47
  @cached_property
60
48
  def with_raw_response(self) -> ExperiencesResourceWithRawResponse:
61
49
  """
62
- This property can be used as a prefix for any HTTP method call to return the
50
+ This property can be used as a prefix for any HTTP method call to return
63
51
  the raw response object instead of the parsed content.
64
52
 
65
53
  For more information, see https://www.github.com/Pay-i/pay-i-python#accessing-raw-response-data-eg-headers
@@ -180,10 +168,6 @@ class AsyncExperiencesResource(AsyncAPIResource):
180
168
  def types(self) -> AsyncTypesResource:
181
169
  return AsyncTypesResource(self._client)
182
170
 
183
- @cached_property
184
- def csat(self) -> AsyncCsatResource:
185
- return AsyncCsatResource(self._client)
186
-
187
171
  @cached_property
188
172
  def properties(self) -> AsyncPropertiesResource:
189
173
  return AsyncPropertiesResource(self._client)
@@ -191,7 +175,7 @@ class AsyncExperiencesResource(AsyncAPIResource):
191
175
  @cached_property
192
176
  def with_raw_response(self) -> AsyncExperiencesResourceWithRawResponse:
193
177
  """
194
- This property can be used as a prefix for any HTTP method call to return the
178
+ This property can be used as a prefix for any HTTP method call to return
195
179
  the raw response object instead of the parsed content.
196
180
 
197
181
  For more information, see https://www.github.com/Pay-i/pay-i-python#accessing-raw-response-data-eg-headers
@@ -325,10 +309,6 @@ class ExperiencesResourceWithRawResponse:
325
309
  def types(self) -> TypesResourceWithRawResponse:
326
310
  return TypesResourceWithRawResponse(self._experiences.types)
327
311
 
328
- @cached_property
329
- def csat(self) -> CsatResourceWithRawResponse:
330
- return CsatResourceWithRawResponse(self._experiences.csat)
331
-
332
312
  @cached_property
333
313
  def properties(self) -> PropertiesResourceWithRawResponse:
334
314
  return PropertiesResourceWithRawResponse(self._experiences.properties)
@@ -352,10 +332,6 @@ class AsyncExperiencesResourceWithRawResponse:
352
332
  def types(self) -> AsyncTypesResourceWithRawResponse:
353
333
  return AsyncTypesResourceWithRawResponse(self._experiences.types)
354
334
 
355
- @cached_property
356
- def csat(self) -> AsyncCsatResourceWithRawResponse:
357
- return AsyncCsatResourceWithRawResponse(self._experiences.csat)
358
-
359
335
  @cached_property
360
336
  def properties(self) -> AsyncPropertiesResourceWithRawResponse:
361
337
  return AsyncPropertiesResourceWithRawResponse(self._experiences.properties)
@@ -379,10 +355,6 @@ class ExperiencesResourceWithStreamingResponse:
379
355
  def types(self) -> TypesResourceWithStreamingResponse:
380
356
  return TypesResourceWithStreamingResponse(self._experiences.types)
381
357
 
382
- @cached_property
383
- def csat(self) -> CsatResourceWithStreamingResponse:
384
- return CsatResourceWithStreamingResponse(self._experiences.csat)
385
-
386
358
  @cached_property
387
359
  def properties(self) -> PropertiesResourceWithStreamingResponse:
388
360
  return PropertiesResourceWithStreamingResponse(self._experiences.properties)
@@ -406,10 +378,6 @@ class AsyncExperiencesResourceWithStreamingResponse:
406
378
  def types(self) -> AsyncTypesResourceWithStreamingResponse:
407
379
  return AsyncTypesResourceWithStreamingResponse(self._experiences.types)
408
380
 
409
- @cached_property
410
- def csat(self) -> AsyncCsatResourceWithStreamingResponse:
411
- return AsyncCsatResourceWithStreamingResponse(self._experiences.csat)
412
-
413
381
  @cached_property
414
382
  def properties(self) -> AsyncPropertiesResourceWithStreamingResponse:
415
383
  return AsyncPropertiesResourceWithStreamingResponse(self._experiences.properties)