From eaf302689cbd6feb4c73bd790e50c92a2f673382 Mon Sep 17 00:00:00 2001 From: Tommaso Barbugli Date: Fri, 26 Sep 2025 14:57:55 +0200 Subject: [PATCH 01/12] wip --- getstream/base.py | 221 +++++++++++++++-- getstream/chat/async_channel.py | 14 ++ getstream/chat/channel.py | 15 ++ getstream/common/telemetry.py | 354 ++++++++++++++++++++++++++++ getstream/stream.py | 5 + getstream/video/async_call.py | 38 +++ getstream/video/call.py | 38 +++ pyproject.toml | 5 + tests/test_operation_name.py | 31 +++ tests/test_tracing_jaeger_manual.py | 67 ++++++ tests/test_tracing_logical_names.py | 44 ++++ tests/test_video_examples.py | 140 +++++++++++ uv.lock | 156 +++++++++++- 13 files changed, 1105 insertions(+), 23 deletions(-) create mode 100644 getstream/common/telemetry.py create mode 100644 tests/test_operation_name.py create mode 100644 tests/test_tracing_jaeger_manual.py create mode 100644 tests/test_tracing_logical_names.py diff --git a/getstream/base.py b/getstream/base.py index 0e941f3c..7b600ee2 100644 --- a/getstream/base.py +++ b/getstream/base.py @@ -1,4 +1,6 @@ import json +import time +import inspect from typing import Any, Dict, Optional, Type, get_origin from getstream.models import APIError @@ -9,6 +11,12 @@ from getstream.config import BaseConfig from urllib.parse import quote from abc import ABC +from getstream.common.telemetry import ( + common_attributes, + record_metrics, + span_request, + current_operation, +) def build_path(path: str, path_params: dict) -> str: @@ -73,6 +81,74 @@ def __enter__(self): def __exit__(self, exc_type, exc_val, exc_tb): self.close() + def _normalize_endpoint_from_path(self, path: str) -> str: + # Convert /api/v2/video/call/{type}/{id} -> api.v2.video.call.$type.$id + norm_parts = [] + for p in path.strip("/").split("/"): + if not p: + continue + if p.startswith("{") and p.endswith("}"): + name = p[1:-1].strip() + if name: + norm_parts.append(f"${name}") + else: + norm_parts.append(p) + return ".".join(norm_parts) if norm_parts else "root" + + def _endpoint_name(self, path: str) -> str: + op = current_operation(None) + if op: + return op + try: + frame = inspect.currentframe() + caller = frame.f_back # BaseClient. + op = caller.f_back.f_code.co_name if caller and caller.f_back else None + except Exception: + op = None + if op: + return f"{self.__class__.__name__}.{op}" + return self._normalize_endpoint_from_path(path) + + def _request_sync( + self, method: str, path: str, *, query_params=None, args=(), kwargs=None + ): + kwargs = kwargs or {} + path_params = kwargs.get("path_params") + url_path = ( + build_path(path, path_params) if path_params else build_path(path, None) + ) + url_full = f"{self.base_url}{url_path}" + endpoint = self._endpoint_name(path) + attrs = common_attributes( + api_key=self.api_key, endpoint=endpoint, method=method, url=url_full + ) + start = time.perf_counter() + # Span name uses logical operation (endpoint) rather than raw HTTP + with span_request( + endpoint, attributes=attrs, request_body=kwargs.get("json") + ) as span: + call_kwargs = dict(kwargs) + call_kwargs.pop("path_params", None) + response = getattr(self.client, method.lower())( + url_path, params=query_params, *args, **call_kwargs + ) + try: + span and span.set_attribute( + "http.response.status_code", response.status_code + ) + except Exception: + pass + duration_ms = (time.perf_counter() - start) * 1000.0 + metric_attrs = common_attributes( + api_key=self.api_key, + endpoint=endpoint, + method=method, + url=url_full, + status_code=getattr(response, "status_code", None), + ) + record_metrics(duration_ms, attributes=metric_attrs) + return response + def patch( self, path, @@ -82,8 +158,12 @@ def patch( *args, **kwargs, ) -> StreamResponse[T]: - response = self.client.patch( - build_path(path, path_params), params=query_params, *args, **kwargs + response = self._request_sync( + "PATCH", + path, + query_params=query_params, + args=args, + kwargs=kwargs | {"path_params": path_params}, ) return self._parse_response(response, data_type or Dict[str, Any]) @@ -96,8 +176,12 @@ def get( *args, **kwargs, ) -> StreamResponse[T]: - response = self.client.get( - build_path(path, path_params), params=query_params, *args, **kwargs + response = self._request_sync( + "GET", + path, + query_params=query_params, + args=args, + kwargs=kwargs | {"path_params": path_params}, ) return self._parse_response(response, data_type or Dict[str, Any]) @@ -110,10 +194,13 @@ def post( *args, **kwargs, ) -> StreamResponse[T]: - response = self.client.post( - build_path(path, path_params), params=query_params, *args, **kwargs + response = self._request_sync( + "POST", + path, + query_params=query_params, + args=args, + kwargs=kwargs | {"path_params": path_params}, ) - return self._parse_response(response, data_type or Dict[str, Any]) def put( @@ -125,8 +212,12 @@ def put( *args, **kwargs, ) -> StreamResponse[T]: - response = self.client.put( - build_path(path, path_params), params=query_params, *args, **kwargs + response = self._request_sync( + "PUT", + path, + query_params=query_params, + args=args, + kwargs=kwargs | {"path_params": path_params}, ) return self._parse_response(response, data_type or Dict[str, Any]) @@ -139,8 +230,12 @@ def delete( *args, **kwargs, ) -> StreamResponse[T]: - response = self.client.delete( - build_path(path, path_params), params=query_params, *args, **kwargs + response = self._request_sync( + "DELETE", + path, + query_params=query_params, + args=args, + kwargs=kwargs | {"path_params": path_params}, ) return self._parse_response(response, data_type or Dict[str, Any]) @@ -182,6 +277,69 @@ async def aclose(self): """Close HTTPX async client (closes pools/keep-alives).""" await self.client.aclose() + def _normalize_endpoint_from_path(self, path: str) -> str: + norm_parts = [] + for p in path.strip("/").split("/"): + if not p: + continue + if p.startswith("{") and p.endswith("}"): + name = p[1:-1].strip() + if name: + norm_parts.append(f"${name}") + else: + norm_parts.append(p) + return ".".join(norm_parts) if norm_parts else "root" + + def _endpoint_name(self, path: str) -> str: + try: + frame = inspect.currentframe() + caller = frame.f_back + op = caller.f_back.f_code.co_name if caller and caller.f_back else None + except Exception: + op = None + if op: + return f"{self.__class__.__name__}.{op}" + return self._normalize_endpoint_from_path(path) + + async def _request_async( + self, method: str, path: str, *, query_params=None, args=(), kwargs=None + ): + kwargs = kwargs or {} + path_params = kwargs.get("path_params") + url_path = ( + build_path(path, path_params) if path_params else build_path(path, None) + ) + url_full = f"{self.base_url}{url_path}" + endpoint = self._endpoint_name(path) + attrs = common_attributes( + api_key=self.api_key, endpoint=endpoint, method=method, url=url_full + ) + start = time.perf_counter() + with span_request( + endpoint, attributes=attrs, request_body=kwargs.get("json") + ) as span: + call_kwargs = dict(kwargs) + call_kwargs.pop("path_params", None) + response = await getattr(self.client, method.lower())( + url_path, params=query_params, *args, **call_kwargs + ) + try: + span and span.set_attribute( + "http.response.status_code", response.status_code + ) + except Exception: + pass + duration_ms = (time.perf_counter() - start) * 1000.0 + metric_attrs = common_attributes( + api_key=self.api_key, + endpoint=endpoint, + method=method, + url=url_full, + status_code=getattr(response, "status_code", None), + ) + record_metrics(duration_ms, attributes=metric_attrs) + return response + async def patch( self, path, @@ -191,8 +349,12 @@ async def patch( *args, **kwargs, ) -> StreamResponse[T]: - response = await self.client.patch( - build_path(path, path_params), params=query_params, *args, **kwargs + response = await self._request_async( + "PATCH", + path, + query_params=query_params, + args=args, + kwargs=kwargs | {"path_params": path_params}, ) return self._parse_response(response, data_type or Dict[str, Any]) @@ -205,8 +367,12 @@ async def get( *args, **kwargs, ) -> StreamResponse[T]: - response = await self.client.get( - build_path(path, path_params), params=query_params, *args, **kwargs + response = await self._request_async( + "GET", + path, + query_params=query_params, + args=args, + kwargs=kwargs | {"path_params": path_params}, ) return self._parse_response(response, data_type or Dict[str, Any]) @@ -219,10 +385,13 @@ async def post( *args, **kwargs, ) -> StreamResponse[T]: - response = await self.client.post( - build_path(path, path_params), params=query_params, *args, **kwargs + response = await self._request_async( + "POST", + path, + query_params=query_params, + args=args, + kwargs=kwargs | {"path_params": path_params}, ) - return self._parse_response(response, data_type or Dict[str, Any]) async def put( @@ -234,8 +403,12 @@ async def put( *args, **kwargs, ) -> StreamResponse[T]: - response = await self.client.put( - build_path(path, path_params), params=query_params, *args, **kwargs + response = await self._request_async( + "PUT", + path, + query_params=query_params, + args=args, + kwargs=kwargs | {"path_params": path_params}, ) return self._parse_response(response, data_type or Dict[str, Any]) @@ -248,8 +421,12 @@ async def delete( *args, **kwargs, ) -> StreamResponse[T]: - response = await self.client.delete( - build_path(path, path_params), params=query_params, *args, **kwargs + response = await self._request_async( + "DELETE", + path, + query_params=query_params, + args=args, + kwargs=kwargs | {"path_params": path_params}, ) return self._parse_response(response, data_type or Dict[str, Any]) diff --git a/getstream/chat/async_channel.py b/getstream/chat/async_channel.py index ff434208..8749046e 100644 --- a/getstream/chat/async_channel.py +++ b/getstream/chat/async_channel.py @@ -1,6 +1,7 @@ # Code generated by GetStream internal OpenAPI code generator. DO NOT EDIT. from getstream.models import * from getstream.stream_response import StreamResponse +from getstream.common.telemetry import attach_channel_cid_async class Channel: @@ -20,6 +21,7 @@ def _sync_from_response(self, data): if hasattr(data, "channel") and isinstance(data.channel, ChannelResponse): self.custom_data = data.channel.custom + @attach_channel_cid_async async def delete( self, hard_delete: Optional[bool] = None ) -> StreamResponse[DeleteChannelResponse]: @@ -29,6 +31,7 @@ async def delete( self._sync_from_response(response.data) return response + @attach_channel_cid_async async def update_channel_partial( self, user_id: Optional[str] = None, @@ -47,6 +50,7 @@ async def update_channel_partial( self._sync_from_response(response.data) return response + @attach_channel_cid_async async def update( self, accept_invite: Optional[bool] = None, @@ -87,6 +91,7 @@ async def update( self._sync_from_response(response.data) return response + @attach_channel_cid_async async def delete_draft( self, parent_id: Optional[str] = None, user_id: Optional[str] = None ) -> StreamResponse[Response]: @@ -99,6 +104,7 @@ async def delete_draft( self._sync_from_response(response.data) return response + @attach_channel_cid_async async def get_draft( self, parent_id: Optional[str] = None, user_id: Optional[str] = None ) -> StreamResponse[GetDraftResponse]: @@ -111,6 +117,7 @@ async def get_draft( self._sync_from_response(response.data) return response + @attach_channel_cid_async async def send_event(self, event: EventRequest) -> StreamResponse[EventResponse]: response = await self.client.send_event( type=self.channel_type, id=self.channel_id, event=event @@ -118,6 +125,7 @@ async def send_event(self, event: EventRequest) -> StreamResponse[EventResponse] self._sync_from_response(response.data) return response + @attach_channel_cid_async async def delete_file(self, url: Optional[str] = None) -> StreamResponse[Response]: response = await self.client.delete_file( type=self.channel_type, id=self.channel_id, url=url @@ -125,6 +133,7 @@ async def delete_file(self, url: Optional[str] = None) -> StreamResponse[Respons self._sync_from_response(response.data) return response + @attach_channel_cid_async async def upload_file( self, file: Optional[str] = None, user: Optional[OnlyUserID] = None ) -> StreamResponse[FileUploadResponse]: @@ -134,6 +143,7 @@ async def upload_file( self._sync_from_response(response.data) return response + @attach_channel_cid_async async def hide( self, clear_history: Optional[bool] = None, @@ -150,6 +160,7 @@ async def hide( self._sync_from_response(response.data) return response + @attach_channel_cid_async async def delete_image(self, url: Optional[str] = None) -> StreamResponse[Response]: response = await self.client.delete_image( type=self.channel_type, id=self.channel_id, url=url @@ -157,6 +168,7 @@ async def delete_image(self, url: Optional[str] = None) -> StreamResponse[Respon self._sync_from_response(response.data) return response + @attach_channel_cid_async async def upload_image( self, file: Optional[str] = None, @@ -173,6 +185,7 @@ async def upload_image( self._sync_from_response(response.data) return response + @attach_channel_cid_async async def update_member_partial( self, user_id: Optional[str] = None, @@ -189,6 +202,7 @@ async def update_member_partial( self._sync_from_response(response.data) return response + @attach_channel_cid_async async def send_message( self, message: MessageRequest, diff --git a/getstream/chat/channel.py b/getstream/chat/channel.py index 0eecd1ce..80dbb5ac 100644 --- a/getstream/chat/channel.py +++ b/getstream/chat/channel.py @@ -1,6 +1,7 @@ # Code generated by GetStream internal OpenAPI code generator. DO NOT EDIT. from getstream.models import * from getstream.stream_response import StreamResponse +from getstream.common.telemetry import attach_channel_cid class Channel: @@ -20,6 +21,7 @@ def _sync_from_response(self, data): if hasattr(data, "channel") and isinstance(data.channel, ChannelResponse): self.custom_data = data.channel.custom + @attach_channel_cid def delete( self, hard_delete: Optional[bool] = None ) -> StreamResponse[DeleteChannelResponse]: @@ -29,6 +31,7 @@ def delete( self._sync_from_response(response.data) return response + @attach_channel_cid def update_channel_partial( self, user_id: Optional[str] = None, @@ -47,6 +50,7 @@ def update_channel_partial( self._sync_from_response(response.data) return response + @attach_channel_cid def update( self, accept_invite: Optional[bool] = None, @@ -87,6 +91,7 @@ def update( self._sync_from_response(response.data) return response + @attach_channel_cid def delete_draft( self, parent_id: Optional[str] = None, user_id: Optional[str] = None ) -> StreamResponse[Response]: @@ -99,6 +104,7 @@ def delete_draft( self._sync_from_response(response.data) return response + @attach_channel_cid def get_draft( self, parent_id: Optional[str] = None, user_id: Optional[str] = None ) -> StreamResponse[GetDraftResponse]: @@ -111,6 +117,7 @@ def get_draft( self._sync_from_response(response.data) return response + @attach_channel_cid def send_event(self, event: EventRequest) -> StreamResponse[EventResponse]: response = self.client.send_event( type=self.channel_type, id=self.channel_id, event=event @@ -118,6 +125,7 @@ def send_event(self, event: EventRequest) -> StreamResponse[EventResponse]: self._sync_from_response(response.data) return response + @attach_channel_cid def delete_file(self, url: Optional[str] = None) -> StreamResponse[Response]: response = self.client.delete_file( type=self.channel_type, id=self.channel_id, url=url @@ -125,6 +133,7 @@ def delete_file(self, url: Optional[str] = None) -> StreamResponse[Response]: self._sync_from_response(response.data) return response + @attach_channel_cid def upload_file( self, file: Optional[str] = None, user: Optional[OnlyUserID] = None ) -> StreamResponse[FileUploadResponse]: @@ -134,6 +143,7 @@ def upload_file( self._sync_from_response(response.data) return response + @attach_channel_cid def hide( self, clear_history: Optional[bool] = None, @@ -150,6 +160,7 @@ def hide( self._sync_from_response(response.data) return response + @attach_channel_cid def delete_image(self, url: Optional[str] = None) -> StreamResponse[Response]: response = self.client.delete_image( type=self.channel_type, id=self.channel_id, url=url @@ -157,6 +168,7 @@ def delete_image(self, url: Optional[str] = None) -> StreamResponse[Response]: self._sync_from_response(response.data) return response + @attach_channel_cid def upload_image( self, file: Optional[str] = None, @@ -173,6 +185,7 @@ def upload_image( self._sync_from_response(response.data) return response + @attach_channel_cid def update_member_partial( self, user_id: Optional[str] = None, @@ -189,6 +202,7 @@ def update_member_partial( self._sync_from_response(response.data) return response + @attach_channel_cid def send_message( self, message: MessageRequest, @@ -222,6 +236,7 @@ def get_many_messages( self._sync_from_response(response.data) return response + @attach_channel_cid def get_or_create( self, hide_for_creator: Optional[bool] = None, diff --git a/getstream/common/telemetry.py b/getstream/common/telemetry.py new file mode 100644 index 00000000..1018fe0c --- /dev/null +++ b/getstream/common/telemetry.py @@ -0,0 +1,354 @@ +from __future__ import annotations + +import json +import os +import time +from contextlib import contextmanager +from typing import Any, Dict, Optional, Callable, Awaitable +import inspect + +# Optional OpenTelemetry imports with graceful fallback +try: + from opentelemetry import baggage, context as otel_context, metrics, trace + from opentelemetry.trace import SpanKind, Status, StatusCode + + _HAS_OTEL = True +except Exception: # pragma: no cover - fallback when OTel not installed + baggage = None # type: ignore + otel_context = None # type: ignore + metrics = None # type: ignore + trace = None # type: ignore + SpanKind = object # type: ignore + Status = object # type: ignore + StatusCode = object # type: ignore + _HAS_OTEL = False + + +# Env-driven config +INCLUDE_BODIES = os.getenv("STREAM_OTEL_INCLUDE_BODIES", "false").lower() in { + "1", + "true", + "yes", +} +MAX_BODY_CHARS = int(os.getenv("STREAM_OTEL_MAX_BODY_CHARS", "2048")) +REDACT_KEYS = { + k.strip().lower() + for k in os.getenv( + "STREAM_OTEL_REDACT_KEYS", + "authorization,password,token,secret,api_key,authorization_bearer,auth", + ).split(",") +} + + +def _noop_cm(): # pragma: no cover - used when OTel missing + @contextmanager + def _inner(*_args, **_kwargs): + yield None + + return _inner() + + +if _HAS_OTEL: + _TRACER = trace.get_tracer("getstream") + _METER = metrics.get_meter("getstream") + + REQ_HIST = _METER.create_histogram( + name="getstream.client.request.duration", + unit="ms", + description="SDK client request latency", + ) + REQ_COUNT = _METER.create_counter( + name="getstream.client.request.count", + description="SDK client requests", + ) +else: # pragma: no cover - no-op instruments + _TRACER = None + REQ_HIST = None + REQ_COUNT = None + + +def redact_api_key(api_key: str | None) -> str | None: + if not api_key: + return None + if len(api_key) <= 6: + return "***" + return f"{api_key[:6]}***" + + +def safe_dump(payload: Any, max_chars: int | None = None) -> str: + if max_chars is None: + max_chars = MAX_BODY_CHARS + + def _redact(obj: Any) -> Any: + if isinstance(obj, dict): + return { + k: ("***" if k.lower() in REDACT_KEYS else _redact(v)) + for k, v in obj.items() + } + if isinstance(obj, list): + return [_redact(v) for v in obj] + return obj + + try: + s = json.dumps(_redact(payload), ensure_ascii=False) + except Exception: + s = str(payload) + return s[:max_chars] + + +def common_attributes( + *, + api_key: Optional[str], + endpoint: str, + method: str, + url: Optional[str] = None, + status_code: Optional[int] = None, +) -> Dict[str, Any]: + attrs: Dict[str, Any] = { + "stream.endpoint": endpoint, + "http.request.method": method, + } + if url: + attrs["url.full"] = url + if status_code is not None: + attrs["http.response.status_code"] = int(status_code) + red = redact_api_key(api_key) + if red: + attrs["stream.api_key"] = red + if _HAS_OTEL and baggage is not None: + call_cid = baggage.get_baggage("stream.call_cid") + if call_cid: + attrs["stream.call_cid"] = call_cid + channel_cid = baggage.get_baggage("stream.channel_cid") + if channel_cid: + attrs["stream.channel_cid"] = channel_cid + return attrs + + +def record_metrics(duration_ms: float, *, attributes: Dict[str, Any]) -> None: + if not _HAS_OTEL or REQ_HIST is None or REQ_COUNT is None: + return + REQ_HIST.record(duration_ms, attributes=attributes) + REQ_COUNT.add(1, attributes=attributes) + + +@contextmanager +def span_request( + name: str, + *, + attributes: Optional[Dict[str, Any]] = None, + include_bodies: Optional[bool] = None, + request_body: Any = None, +): + """Start a CLIENT span if OTel is available; otherwise no-op. + + Records exceptions and sets status=ERROR. Adds request body as an event + when enabled. Records duration on the span as attribute for debugging. + """ + include_bodies = INCLUDE_BODIES if include_bodies is None else include_bodies + if not _HAS_OTEL or _TRACER is None: # pragma: no cover + start = time.perf_counter() + try: + yield None + finally: + _ = time.perf_counter() - start + return + + start = time.perf_counter() + with _TRACER.start_as_current_span(name, kind=SpanKind.CLIENT) as span: # type: ignore[arg-type] + if attributes: + try: + span.set_attributes(attributes) # type: ignore[attr-defined] + except Exception: + pass + if include_bodies and request_body is not None: + try: + span.add_event( + "request.body", {"level": "INFO", "body": safe_dump(request_body)} + ) # type: ignore[attr-defined] + except Exception: + pass + try: + yield span + except BaseException as e: + try: + span.record_exception(e) # type: ignore[attr-defined] + span.set_status(Status(StatusCode.ERROR, str(e))) # type: ignore[attr-defined] + except Exception: + pass + raise + finally: + duration_ms = (time.perf_counter() - start) * 1000.0 + try: + span.set_attribute("getstream.request.duration_ms", duration_ms) # type: ignore[attr-defined] + except Exception: + pass + + +@contextmanager +def with_call_context(call_cid: str): + """Attach `stream.call_cid` baggage for the scope of the context (no-op if OTel missing).""" + if not _HAS_OTEL or baggage is None or otel_context is None: # pragma: no cover + yield + return + ctx = baggage.set_baggage( + "stream.call_cid", call_cid, context=otel_context.get_current() + ) + token = otel_context.attach(ctx) + try: + yield + finally: + otel_context.detach(token) + + +def current_operation(default: Optional[str] = None) -> Optional[str]: + """Return current logical operation name from baggage (stream.operation).""" + if not _HAS_OTEL or baggage is None: + return default + op = baggage.get_baggage("stream.operation") + return op or default + + +# Decorators for auto-attaching baggage around method calls +def attach_call_cid(func: Callable[..., Any]) -> Callable[..., Any]: + def wrapper(self, *args, **kwargs): + cid = None + try: + cid = f"{getattr(self, 'call_type', None)}:{getattr(self, 'id', None)}" + except Exception: + cid = None + op = f"call.{getattr(func, '__name__', 'unknown')}" + if cid and _HAS_OTEL and baggage is not None and otel_context is not None: + ctx = baggage.set_baggage( + "stream.call_cid", cid, context=otel_context.get_current() + ) + ctx = baggage.set_baggage("stream.operation", op, context=ctx) + token = otel_context.attach(ctx) + try: + return func(self, *args, **kwargs) + finally: + otel_context.detach(token) + return func(self, *args, **kwargs) + + return wrapper + + +def attach_call_cid_async( + func: Callable[..., Awaitable[Any]], +) -> Callable[..., Awaitable[Any]]: + async def wrapper(self, *args, **kwargs): + cid = None + try: + cid = f"{getattr(self, 'call_type', None)}:{getattr(self, 'id', None)}" + except Exception: + cid = None + op = f"call.{getattr(func, '__name__', 'unknown')}" + if cid and _HAS_OTEL and baggage is not None and otel_context is not None: + ctx = baggage.set_baggage( + "stream.call_cid", cid, context=otel_context.get_current() + ) + ctx = baggage.set_baggage("stream.operation", op, context=ctx) + token = otel_context.attach(ctx) + try: + return await func(self, *args, **kwargs) + finally: + otel_context.detach(token) + return await func(self, *args, **kwargs) + + return wrapper + + +def attach_channel_cid(func: Callable[..., Any]) -> Callable[..., Any]: + def wrapper(self, *args, **kwargs): + cid = None + try: + cid = f"{getattr(self, 'channel_type', None)}:{getattr(self, 'channel_id', None)}" + except Exception: + cid = None + op = f"channel.{getattr(func, '__name__', 'unknown')}" + if cid and _HAS_OTEL and baggage is not None and otel_context is not None: + # Attach channel_cid to baggage under a different key than call_cid + ctx = baggage.set_baggage( + "stream.channel_cid", cid, context=otel_context.get_current() + ) + ctx = baggage.set_baggage("stream.operation", op, context=ctx) + token = otel_context.attach(ctx) + try: + return func(self, *args, **kwargs) + finally: + otel_context.detach(token) + return func(self, *args, **kwargs) + + return wrapper + + +def attach_channel_cid_async( + func: Callable[..., Awaitable[Any]], +) -> Callable[..., Awaitable[Any]]: + async def wrapper(self, *args, **kwargs): + cid = None + try: + cid = f"{getattr(self, 'channel_type', None)}:{getattr(self, 'channel_id', None)}" + except Exception: + cid = None + op = f"channel.{getattr(func, '__name__', 'unknown')}" + if cid and _HAS_OTEL and baggage is not None and otel_context is not None: + ctx = baggage.set_baggage( + "stream.channel_cid", cid, context=otel_context.get_current() + ) + ctx = baggage.set_baggage("stream.operation", op, context=ctx) + token = otel_context.attach(ctx) + try: + return await func(self, *args, **kwargs) + finally: + otel_context.detach(token) + return await func(self, *args, **kwargs) + + return wrapper + + +def operation_name( + operation: str, +) -> Callable[[Callable[..., Any]], Callable[..., Any]]: + """Decorator to attach a logical operation name for the wrapped call. + + Usage: + from getstream.common import telemetry + + @telemetry.operation_name("create_user") + def create_user(...): + return self.post(...) + """ + + def decorator(func: Callable[..., Any]) -> Callable[..., Any]: + if inspect.iscoroutinefunction(func): + + async def async_wrapper(*args, **kwargs): + if not _HAS_OTEL or baggage is None or otel_context is None: + return await func(*args, **kwargs) + ctx = baggage.set_baggage( + "stream.operation", operation, context=otel_context.get_current() + ) + token = otel_context.attach(ctx) + try: + return await func(*args, **kwargs) + finally: + otel_context.detach(token) + + return async_wrapper + + def wrapper(*args, **kwargs): + if not _HAS_OTEL or baggage is None or otel_context is None: + return func(*args, **kwargs) + ctx = baggage.set_baggage( + "stream.operation", operation, context=otel_context.get_current() + ) + token = otel_context.attach(ctx) + try: + return func(*args, **kwargs) + finally: + otel_context.detach(token) + + return wrapper + + return decorator diff --git a/getstream/stream.py b/getstream/stream.py index 03a13652..4e2ca1c7 100644 --- a/getstream/stream.py +++ b/getstream/stream.py @@ -6,6 +6,7 @@ import jwt from pydantic_settings import BaseSettings, SettingsConfigDict +from getstream.common import telemetry from getstream.chat.client import ChatClient from getstream.chat.async_client import ChatClient as AsyncChatClient from getstream.common.async_client import CommonClient as AsyncCommonClient @@ -193,6 +194,7 @@ def moderation(self) -> AsyncModerationClient: def feeds(self): raise NotImplementedError("Feeds not supported for async client") + @telemetry.operation_name("create_user") async def create_user(self, name: str = "", id: str = str(uuid4()), image=""): """ Creates or updates users. This method performs an "upsert" operation, @@ -205,6 +207,7 @@ async def create_user(self, name: str = "", id: str = str(uuid4()), image=""): user = response.data.users[user.id] return user + @telemetry.operation_name("upsert_users") async def upsert_users(self, *users: UserRequest): """ Creates or updates users. This method performs an "upsert" operation, @@ -302,6 +305,7 @@ def feeds(self) -> FeedsClient: stream=self, ) + @telemetry.operation_name("create_user") def create_user(self, name: str = "", id: str = str(uuid4()), image=""): """ Creates or updates users. This method performs an "upsert" operation, @@ -314,6 +318,7 @@ def create_user(self, name: str = "", id: str = str(uuid4()), image=""): user = response.data.users[user.id] return user + @telemetry.operation_name("upsert_users") def upsert_users(self, *users: UserRequest): """ Creates or updates users. This method performs an "upsert" operation, diff --git a/getstream/video/async_call.py b/getstream/video/async_call.py index 5273fb64..62f8c010 100644 --- a/getstream/video/async_call.py +++ b/getstream/video/async_call.py @@ -2,9 +2,11 @@ from getstream.models import * from getstream.stream_response import StreamResponse from getstream.video import BaseCall +from getstream.common.telemetry import attach_call_cid_async class Call(BaseCall): + @attach_call_cid_async async def get( self, members_limit: Optional[int] = None, @@ -23,6 +25,7 @@ async def get( self._sync_from_response(response.data) return response + @attach_call_cid_async async def update( self, starts_at: Optional[datetime] = None, @@ -39,6 +42,7 @@ async def update( self._sync_from_response(response.data) return response + @attach_call_cid_async async def get_or_create( self, members_limit: Optional[int] = None, @@ -59,6 +63,7 @@ async def get_or_create( self._sync_from_response(response.data) return response + @attach_call_cid_async async def block_user(self, user_id: str) -> StreamResponse[BlockUserResponse]: response = await self.client.block_user( type=self.call_type, id=self.id, user_id=user_id @@ -66,6 +71,7 @@ async def block_user(self, user_id: str) -> StreamResponse[BlockUserResponse]: self._sync_from_response(response.data) return response + @attach_call_cid_async async def send_closed_caption( self, speaker_id: str, @@ -94,6 +100,7 @@ async def send_closed_caption( self._sync_from_response(response.data) return response + @attach_call_cid_async async def delete( self, hard: Optional[bool] = None ) -> StreamResponse[DeleteCallResponse]: @@ -103,6 +110,7 @@ async def delete( self._sync_from_response(response.data) return response + @attach_call_cid_async async def send_call_event( self, user_id: Optional[str] = None, @@ -115,6 +123,7 @@ async def send_call_event( self._sync_from_response(response.data) return response + @attach_call_cid_async async def collect_user_feedback( self, rating: int, @@ -137,6 +146,7 @@ async def collect_user_feedback( self._sync_from_response(response.data) return response + @attach_call_cid_async async def go_live( self, recording_storage_name: Optional[str] = None, @@ -159,6 +169,7 @@ async def go_live( self._sync_from_response(response.data) return response + @attach_call_cid_async async def kick_user( self, user_id: str, @@ -177,11 +188,13 @@ async def kick_user( self._sync_from_response(response.data) return response + @attach_call_cid_async async def end(self) -> StreamResponse[EndCallResponse]: response = await self.client.end_call(type=self.call_type, id=self.id) self._sync_from_response(response.data) return response + @attach_call_cid_async async def update_call_members( self, remove_members: Optional[List[str]] = None, @@ -196,6 +209,7 @@ async def update_call_members( self._sync_from_response(response.data) return response + @attach_call_cid_async async def mute_users( self, audio: Optional[bool] = None, @@ -222,6 +236,7 @@ async def mute_users( self._sync_from_response(response.data) return response + @attach_call_cid_async async def query_call_participants( self, limit: Optional[int] = None, @@ -236,6 +251,7 @@ async def query_call_participants( self._sync_from_response(response.data) return response + @attach_call_cid_async async def video_pin( self, session_id: str, user_id: str ) -> StreamResponse[PinResponse]: @@ -245,11 +261,13 @@ async def video_pin( self._sync_from_response(response.data) return response + @attach_call_cid_async async def list_recordings(self) -> StreamResponse[ListRecordingsResponse]: response = await self.client.list_recordings(type=self.call_type, id=self.id) self._sync_from_response(response.data) return response + @attach_call_cid_async async def get_call_report( self, session_id: Optional[str] = None ) -> StreamResponse[GetCallReportResponse]: @@ -259,6 +277,7 @@ async def get_call_report( self._sync_from_response(response.data) return response + @attach_call_cid_async async def start_rtmp_broadcasts( self, broadcasts: List[RTMPBroadcastRequest] ) -> StreamResponse[StartRTMPBroadcastsResponse]: @@ -268,6 +287,7 @@ async def start_rtmp_broadcasts( self._sync_from_response(response.data) return response + @attach_call_cid_async async def stop_all_rtmp_broadcasts( self, ) -> StreamResponse[StopAllRTMPBroadcastsResponse]: @@ -277,6 +297,7 @@ async def stop_all_rtmp_broadcasts( self._sync_from_response(response.data) return response + @attach_call_cid_async async def stop_rtmp_broadcast( self, name: str, @@ -289,6 +310,7 @@ async def stop_rtmp_broadcast( self._sync_from_response(response.data) return response + @attach_call_cid_async async def start_hls_broadcasting( self, ) -> StreamResponse[StartHLSBroadcastingResponse]: @@ -298,6 +320,7 @@ async def start_hls_broadcasting( self._sync_from_response(response.data) return response + @attach_call_cid_async async def start_closed_captions( self, enable_transcription: Optional[bool] = None, @@ -316,6 +339,7 @@ async def start_closed_captions( self._sync_from_response(response.data) return response + @attach_call_cid_async async def start_frame_recording( self, recording_external_storage: Optional[str] = None ) -> StreamResponse[StartFrameRecordingResponse]: @@ -327,6 +351,7 @@ async def start_frame_recording( self._sync_from_response(response.data) return response + @attach_call_cid_async async def start_recording( self, recording_external_storage: Optional[str] = None ) -> StreamResponse[StartRecordingResponse]: @@ -338,6 +363,7 @@ async def start_recording( self._sync_from_response(response.data) return response + @attach_call_cid_async async def start_transcription( self, enable_closed_captions: Optional[bool] = None, @@ -354,6 +380,7 @@ async def start_transcription( self._sync_from_response(response.data) return response + @attach_call_cid_async async def stop_hls_broadcasting( self, ) -> StreamResponse[StopHLSBroadcastingResponse]: @@ -363,6 +390,7 @@ async def stop_hls_broadcasting( self._sync_from_response(response.data) return response + @attach_call_cid_async async def stop_closed_captions( self, stop_transcription: Optional[bool] = None ) -> StreamResponse[StopClosedCaptionsResponse]: @@ -372,6 +400,7 @@ async def stop_closed_captions( self._sync_from_response(response.data) return response + @attach_call_cid_async async def stop_frame_recording(self) -> StreamResponse[StopFrameRecordingResponse]: response = await self.client.stop_frame_recording( type=self.call_type, id=self.id @@ -379,6 +408,7 @@ async def stop_frame_recording(self) -> StreamResponse[StopFrameRecordingRespons self._sync_from_response(response.data) return response + @attach_call_cid_async async def stop_live( self, continue_closed_caption: Optional[bool] = None, @@ -399,11 +429,13 @@ async def stop_live( self._sync_from_response(response.data) return response + @attach_call_cid_async async def stop_recording(self) -> StreamResponse[StopRecordingResponse]: response = await self.client.stop_recording(type=self.call_type, id=self.id) self._sync_from_response(response.data) return response + @attach_call_cid_async async def stop_transcription( self, stop_closed_captions: Optional[bool] = None ) -> StreamResponse[StopTranscriptionResponse]: @@ -413,6 +445,7 @@ async def stop_transcription( self._sync_from_response(response.data) return response + @attach_call_cid_async async def list_transcriptions(self) -> StreamResponse[ListTranscriptionsResponse]: response = await self.client.list_transcriptions( type=self.call_type, id=self.id @@ -420,6 +453,7 @@ async def list_transcriptions(self) -> StreamResponse[ListTranscriptionsResponse self._sync_from_response(response.data) return response + @attach_call_cid_async async def unblock_user(self, user_id: str) -> StreamResponse[UnblockUserResponse]: response = await self.client.unblock_user( type=self.call_type, id=self.id, user_id=user_id @@ -427,6 +461,7 @@ async def unblock_user(self, user_id: str) -> StreamResponse[UnblockUserResponse self._sync_from_response(response.data) return response + @attach_call_cid_async async def video_unpin( self, session_id: str, user_id: str ) -> StreamResponse[UnpinResponse]: @@ -436,6 +471,7 @@ async def video_unpin( self._sync_from_response(response.data) return response + @attach_call_cid_async async def update_user_permissions( self, user_id: str, @@ -452,6 +488,7 @@ async def update_user_permissions( self._sync_from_response(response.data) return response + @attach_call_cid_async async def delete_recording( self, session: str, filename: str ) -> StreamResponse[DeleteRecordingResponse]: @@ -461,6 +498,7 @@ async def delete_recording( self._sync_from_response(response.data) return response + @attach_call_cid_async async def delete_transcription( self, session: str, filename: str ) -> StreamResponse[DeleteTranscriptionResponse]: diff --git a/getstream/video/call.py b/getstream/video/call.py index b94dba00..ccddf6bb 100644 --- a/getstream/video/call.py +++ b/getstream/video/call.py @@ -2,9 +2,11 @@ from getstream.models import * from getstream.stream_response import StreamResponse from getstream.video import BaseCall +from getstream.common.telemetry import attach_call_cid class Call(BaseCall): + @attach_call_cid def get( self, members_limit: Optional[int] = None, @@ -23,6 +25,7 @@ def get( self._sync_from_response(response.data) return response + @attach_call_cid def update( self, starts_at: Optional[datetime] = None, @@ -39,6 +42,7 @@ def update( self._sync_from_response(response.data) return response + @attach_call_cid def get_or_create( self, members_limit: Optional[int] = None, @@ -59,6 +63,7 @@ def get_or_create( self._sync_from_response(response.data) return response + @attach_call_cid def block_user(self, user_id: str) -> StreamResponse[BlockUserResponse]: response = self.client.block_user( type=self.call_type, id=self.id, user_id=user_id @@ -66,6 +71,7 @@ def block_user(self, user_id: str) -> StreamResponse[BlockUserResponse]: self._sync_from_response(response.data) return response + @attach_call_cid def send_closed_caption( self, speaker_id: str, @@ -94,11 +100,13 @@ def send_closed_caption( self._sync_from_response(response.data) return response + @attach_call_cid def delete(self, hard: Optional[bool] = None) -> StreamResponse[DeleteCallResponse]: response = self.client.delete_call(type=self.call_type, id=self.id, hard=hard) self._sync_from_response(response.data) return response + @attach_call_cid def send_call_event( self, user_id: Optional[str] = None, @@ -111,6 +119,7 @@ def send_call_event( self._sync_from_response(response.data) return response + @attach_call_cid def collect_user_feedback( self, rating: int, @@ -133,6 +142,7 @@ def collect_user_feedback( self._sync_from_response(response.data) return response + @attach_call_cid def go_live( self, recording_storage_name: Optional[str] = None, @@ -155,6 +165,7 @@ def go_live( self._sync_from_response(response.data) return response + @attach_call_cid def kick_user( self, user_id: str, @@ -173,11 +184,13 @@ def kick_user( self._sync_from_response(response.data) return response + @attach_call_cid def end(self) -> StreamResponse[EndCallResponse]: response = self.client.end_call(type=self.call_type, id=self.id) self._sync_from_response(response.data) return response + @attach_call_cid def update_call_members( self, remove_members: Optional[List[str]] = None, @@ -192,6 +205,7 @@ def update_call_members( self._sync_from_response(response.data) return response + @attach_call_cid def mute_users( self, audio: Optional[bool] = None, @@ -218,6 +232,7 @@ def mute_users( self._sync_from_response(response.data) return response + @attach_call_cid def query_call_participants( self, limit: Optional[int] = None, @@ -232,6 +247,7 @@ def query_call_participants( self._sync_from_response(response.data) return response + @attach_call_cid def video_pin(self, session_id: str, user_id: str) -> StreamResponse[PinResponse]: response = self.client.video_pin( type=self.call_type, id=self.id, session_id=session_id, user_id=user_id @@ -239,11 +255,13 @@ def video_pin(self, session_id: str, user_id: str) -> StreamResponse[PinResponse self._sync_from_response(response.data) return response + @attach_call_cid def list_recordings(self) -> StreamResponse[ListRecordingsResponse]: response = self.client.list_recordings(type=self.call_type, id=self.id) self._sync_from_response(response.data) return response + @attach_call_cid def get_call_report( self, session_id: Optional[str] = None ) -> StreamResponse[GetCallReportResponse]: @@ -253,6 +271,7 @@ def get_call_report( self._sync_from_response(response.data) return response + @attach_call_cid def start_rtmp_broadcasts( self, broadcasts: List[RTMPBroadcastRequest] ) -> StreamResponse[StartRTMPBroadcastsResponse]: @@ -262,11 +281,13 @@ def start_rtmp_broadcasts( self._sync_from_response(response.data) return response + @attach_call_cid def stop_all_rtmp_broadcasts(self) -> StreamResponse[StopAllRTMPBroadcastsResponse]: response = self.client.stop_all_rtmp_broadcasts(type=self.call_type, id=self.id) self._sync_from_response(response.data) return response + @attach_call_cid def stop_rtmp_broadcast( self, name: str, @@ -279,11 +300,13 @@ def stop_rtmp_broadcast( self._sync_from_response(response.data) return response + @attach_call_cid def start_hls_broadcasting(self) -> StreamResponse[StartHLSBroadcastingResponse]: response = self.client.start_hls_broadcasting(type=self.call_type, id=self.id) self._sync_from_response(response.data) return response + @attach_call_cid def start_closed_captions( self, enable_transcription: Optional[bool] = None, @@ -302,6 +325,7 @@ def start_closed_captions( self._sync_from_response(response.data) return response + @attach_call_cid def start_frame_recording( self, recording_external_storage: Optional[str] = None ) -> StreamResponse[StartFrameRecordingResponse]: @@ -313,6 +337,7 @@ def start_frame_recording( self._sync_from_response(response.data) return response + @attach_call_cid def start_recording( self, recording_external_storage: Optional[str] = None ) -> StreamResponse[StartRecordingResponse]: @@ -324,6 +349,7 @@ def start_recording( self._sync_from_response(response.data) return response + @attach_call_cid def start_transcription( self, enable_closed_captions: Optional[bool] = None, @@ -340,11 +366,13 @@ def start_transcription( self._sync_from_response(response.data) return response + @attach_call_cid def stop_hls_broadcasting(self) -> StreamResponse[StopHLSBroadcastingResponse]: response = self.client.stop_hls_broadcasting(type=self.call_type, id=self.id) self._sync_from_response(response.data) return response + @attach_call_cid def stop_closed_captions( self, stop_transcription: Optional[bool] = None ) -> StreamResponse[StopClosedCaptionsResponse]: @@ -354,11 +382,13 @@ def stop_closed_captions( self._sync_from_response(response.data) return response + @attach_call_cid def stop_frame_recording(self) -> StreamResponse[StopFrameRecordingResponse]: response = self.client.stop_frame_recording(type=self.call_type, id=self.id) self._sync_from_response(response.data) return response + @attach_call_cid def stop_live( self, continue_closed_caption: Optional[bool] = None, @@ -379,11 +409,13 @@ def stop_live( self._sync_from_response(response.data) return response + @attach_call_cid def stop_recording(self) -> StreamResponse[StopRecordingResponse]: response = self.client.stop_recording(type=self.call_type, id=self.id) self._sync_from_response(response.data) return response + @attach_call_cid def stop_transcription( self, stop_closed_captions: Optional[bool] = None ) -> StreamResponse[StopTranscriptionResponse]: @@ -393,11 +425,13 @@ def stop_transcription( self._sync_from_response(response.data) return response + @attach_call_cid def list_transcriptions(self) -> StreamResponse[ListTranscriptionsResponse]: response = self.client.list_transcriptions(type=self.call_type, id=self.id) self._sync_from_response(response.data) return response + @attach_call_cid def unblock_user(self, user_id: str) -> StreamResponse[UnblockUserResponse]: response = self.client.unblock_user( type=self.call_type, id=self.id, user_id=user_id @@ -405,6 +439,7 @@ def unblock_user(self, user_id: str) -> StreamResponse[UnblockUserResponse]: self._sync_from_response(response.data) return response + @attach_call_cid def video_unpin( self, session_id: str, user_id: str ) -> StreamResponse[UnpinResponse]: @@ -414,6 +449,7 @@ def video_unpin( self._sync_from_response(response.data) return response + @attach_call_cid def update_user_permissions( self, user_id: str, @@ -430,6 +466,7 @@ def update_user_permissions( self._sync_from_response(response.data) return response + @attach_call_cid def delete_recording( self, session: str, filename: str ) -> StreamResponse[DeleteRecordingResponse]: @@ -439,6 +476,7 @@ def delete_recording( self._sync_from_response(response.data) return response + @attach_call_cid def delete_transcription( self, session: str, filename: str ) -> StreamResponse[DeleteTranscriptionResponse]: diff --git a/pyproject.toml b/pyproject.toml index 7bee4dc5..a8941bcc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,6 +56,10 @@ webrtc = [ "websockets>=15.0.1", "websocket-client>=1.8.0", ] +telemetry = [ + "opentelemetry-api>=1.26.0", + "opentelemetry-sdk>=1.26.0", +] [dependency-groups] dev = [ @@ -69,6 +73,7 @@ dev = [ "grpcio-tools>=1.73.1", "mypy-protobuf==3.5.0", "pytest-timeout>=2.3.1", + "opentelemetry-exporter-otlp>=1.37.0", ] [tool.uv.workspace] diff --git a/tests/test_operation_name.py b/tests/test_operation_name.py new file mode 100644 index 00000000..db343e9d --- /dev/null +++ b/tests/test_operation_name.py @@ -0,0 +1,31 @@ +def test_operation_name_decorator_sets_span_name(): + from opentelemetry import trace + from opentelemetry.sdk.trace import TracerProvider + from opentelemetry.sdk.trace.export import SimpleSpanProcessor + from opentelemetry.sdk.trace.export.in_memory_span_exporter import ( + InMemorySpanExporter, + ) + from getstream.common.telemetry import operation_name + from getstream.base import BaseClient + import httpx + + exporter = InMemorySpanExporter() + tp = TracerProvider() + tp.add_span_processor(SimpleSpanProcessor(exporter)) + trace.set_tracer_provider(tp) + + class Dummy(BaseClient): + @operation_name("dummy.op") + def do(self): + return self.get("/ping") + + def handler(request: httpx.Request) -> httpx.Response: + return httpx.Response(200, json={}, request=request) + + transport = httpx.MockTransport(handler) + c = Dummy(api_key="k", base_url="http://t", token="t", timeout=1.0) + c.client = httpx.Client(base_url=c.base_url, transport=transport) + c.do() + + spans = exporter.get_finished_spans() + assert spans and spans[-1].name == "dummy.op" diff --git a/tests/test_tracing_jaeger_manual.py b/tests/test_tracing_jaeger_manual.py new file mode 100644 index 00000000..138be2a4 --- /dev/null +++ b/tests/test_tracing_jaeger_manual.py @@ -0,0 +1,67 @@ +import os +import uuid +import pytest +from getstream import Stream +from models import ChannelInput, CallRequest + + +@pytest.mark.integration +def test_tracing_with_jaeger_manual(client: Stream): + """ + Manual tracing check with Jaeger (no real API calls). + + This test configures OTLP exporters to a local Jaeger all-in-one and + exercises both Video and Chat clients with a mix of successful and + failing requests under mocked HTTP transports. + + How to run Jaeger locally: + + 1) Start Jaeger (OTLP enabled): + docker run --rm -it \ + -e COLLECTOR_OTLP_ENABLED=true \ + -p 16686:16686 -p 4317:4317 -p 4318:4318 \ + jaegertracing/all-in-one:1.51 + + 2) uv run pytest -q -k tracing_with_jaeger_manual -s + + 3) Open the Jaeger UI: + http://localhost:16686 + """ + + try: + from opentelemetry import trace + from opentelemetry.sdk.resources import Resource + from opentelemetry.sdk.trace import TracerProvider + from opentelemetry.sdk.trace.export import BatchSpanProcessor + from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import ( + OTLPSpanExporter, + ) + except ImportError: + pytest.skip("Missing OTel OTLP exporters") + return + + # Configure Jaeger OTLP exporter + endpoint = os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT", "localhost:4317") + resource = Resource.create( + { + "service.name": "getstream-manual-jaeger", + "deployment.environment": "dev", + } + ) + tp = TracerProvider(resource=resource) + exporter = OTLPSpanExporter(endpoint=endpoint, insecure=True) + + tp.add_span_processor(BatchSpanProcessor(exporter)) + trace.set_tracer_provider(tp) + + with trace.get_tracer("jaeger.test").start_as_current_span("start"): + user_id = str(uuid.uuid4()) + client.create_user(name=user_id, id=user_id) + + channel = client.chat.channel("messaging", "123") + channel.get_or_create(data=ChannelInput(created_by_id=user_id)) + + call = client.video.call("default", "123") + call.get_or_create(data=CallRequest(created_by_id=user_id)) + + tp.force_flush(timeout_millis=2000) diff --git a/tests/test_tracing_logical_names.py b/tests/test_tracing_logical_names.py new file mode 100644 index 00000000..2d6b046d --- /dev/null +++ b/tests/test_tracing_logical_names.py @@ -0,0 +1,44 @@ +def _setup_tracer(): + from opentelemetry import trace + from opentelemetry.sdk.trace import TracerProvider + from opentelemetry.sdk.trace.export import SimpleSpanProcessor + from opentelemetry.sdk.trace.export.in_memory_span_exporter import ( + InMemorySpanExporter, + ) + + exporter = InMemorySpanExporter() + provider = trace.get_tracer_provider() + if isinstance(provider, TracerProvider): + provider.add_span_processor(SimpleSpanProcessor(exporter)) + else: + tp = TracerProvider() + tp.add_span_processor(SimpleSpanProcessor(exporter)) + trace.set_tracer_provider(tp) + return exporter + + +def test_chat_channel_span_name_and_endpoint(): + exporter = _setup_tracer() + + from getstream import Stream + from getstream.models import ChannelInput + import httpx + + def handler(request: httpx.Request) -> httpx.Response: + return httpx.Response(200, json={}, request=request) + + transport = httpx.MockTransport(handler) + + client = Stream(api_key="k", api_secret="s", base_url="http://test", timeout=1.0) + chat = client.chat + chat.client = httpx.Client(base_url=chat.base_url, transport=transport) + + channel = chat.channel("messaging", "123") + channel.get_or_create(data=ChannelInput(created_by_id="user-x")) + + spans = exporter.get_finished_spans() + assert spans, "no spans captured" + s = spans[-1] + assert s.name == "channel.get_or_create" + assert s.attributes.get("stream.endpoint") == "channel.get_or_create" + assert s.attributes.get("stream.channel_cid") == "messaging:123" diff --git a/tests/test_video_examples.py b/tests/test_video_examples.py index 9fd05a05..9f97a4c1 100644 --- a/tests/test_video_examples.py +++ b/tests/test_video_examples.py @@ -524,3 +524,143 @@ async def test_srt(async_client: AsyncStream): assert response.data.call.ingress.srt != "" assert call.create_srt_credentials(user_id).address != "" + + +def test_otel_tracing_and_metrics_baseclient(): + """Verify BaseClient emits OTel spans and metrics with attributes.""" + from opentelemetry import trace, metrics + from opentelemetry.sdk.trace import TracerProvider + from opentelemetry.sdk.trace.export import SimpleSpanProcessor + from opentelemetry.sdk.trace.export.in_memory_span_exporter import ( + InMemorySpanExporter, + ) + from opentelemetry.sdk.metrics import MeterProvider + from opentelemetry.sdk.metrics.export import InMemoryMetricReader + + # Configure in-memory exporters; avoid overriding if already set + span_exporter = InMemorySpanExporter() + provider = trace.get_tracer_provider() + if isinstance(provider, TracerProvider): + provider.add_span_processor(SimpleSpanProcessor(span_exporter)) + else: + tp = TracerProvider() + tp.add_span_processor(SimpleSpanProcessor(span_exporter)) + trace.set_tracer_provider(tp) + + metric_reader = InMemoryMetricReader() + mp = MeterProvider(metric_readers=[metric_reader]) + metrics.set_meter_provider(mp) + + # Dummy rest client subclass using BaseClient + from getstream.base import BaseClient + import httpx + + from typing import Dict as _Dict, Any as _Any + + class DummyClient(BaseClient): + def ping(self): + return self.get("/ping", data_type=_Dict[str, _Any]) + + # Mock transport that returns minimal JSON + def handler(request: httpx.Request) -> httpx.Response: + return httpx.Response(200, json={"ok": True}, request=request) + + transport = httpx.MockTransport(handler) + + client = DummyClient( + api_key="test_key_abcdefg", base_url="http://test", token="tok", timeout=1.0 + ) + # Replace underlying httpx client to avoid real network + client.client = httpx.Client(base_url=client.base_url, transport=transport) + + # Perform request + resp = client.ping() + assert resp.status_code() == 200 + assert resp.data["ok"] is True + + # Validate spans + spans = span_exporter.get_finished_spans() + assert len(spans) >= 1 + s = spans[-1] + # Endpoint should reference the client; method may be the RestClient method name fallback + endpoint_attr = s.attributes.get("stream.endpoint") + assert isinstance(endpoint_attr, str) and endpoint_attr.startswith("DummyClient.") + assert s.attributes.get("http.request.method") == "GET" + assert s.attributes.get("http.response.status_code") == 200 + # API key is redacted + assert s.attributes.get("stream.api_key").startswith("test_k") + assert s.attributes.get("stream.api_key").endswith("***") + + # Validate metrics contain our endpoint attribute + md = metric_reader.get_metrics_data() + names_seen = set() + endpoints = set() + for rm in md.resource_metrics: + for sm in rm.scope_metrics: + for metric in sm.metrics: + names_seen.add(metric.name) + if metric.name in ( + "getstream.client.request.duration", + "getstream.client.request.count", + ): + for dp in metric.data.data_points: # type: ignore[attr-defined] + attrs = dict(dp.attributes) # type: ignore[attr-defined] + if "stream.endpoint" in attrs: + endpoints.add(attrs["stream.endpoint"]) + assert { + "getstream.client.request.duration", + "getstream.client.request.count", + }.issubset(names_seen) + assert any( + isinstance(ep, str) and ep.startswith("DummyClient.") for ep in endpoints + ) + + +def test_otel_baggage_call_cid_video(monkeypatch): + """Verify Call auto-attaches call_cid baggage and spans inherit it.""" + from opentelemetry import trace + from opentelemetry.sdk.trace import TracerProvider + from opentelemetry.sdk.trace.export import SimpleSpanProcessor + from opentelemetry.sdk.trace.export.in_memory_span_exporter import ( + InMemorySpanExporter, + ) + + # Tracing only (metrics validated in previous test) + span_exporter = InMemorySpanExporter() + provider = trace.get_tracer_provider() + # If provider is not SDK TracerProvider yet, set it; otherwise, reuse and add processor + if isinstance(provider, TracerProvider): + provider.add_span_processor(SimpleSpanProcessor(span_exporter)) + else: + tp = TracerProvider() + tp.add_span_processor(SimpleSpanProcessor(span_exporter)) + trace.set_tracer_provider(tp) + + # Prepare a Video client with mocked transport + from getstream import Stream + from getstream.video.client import VideoClient + import httpx + + stream = Stream( + api_key="test_key_abcdefg", + api_secret="shhh", + base_url="http://test", + timeout=1.0, + ) + video: VideoClient = stream.video + + # Mock transport to satisfy any request with empty JSON + def handler(request: httpx.Request) -> httpx.Response: + return httpx.Response(200, json={}, request=request) + + transport = httpx.MockTransport(handler) + video.client = httpx.Client(base_url=video.base_url, transport=transport) + + call = video.call("default", "cid123") + # Execute a simple GET call (will parse empty body permissively) + call.get() + + spans = span_exporter.get_finished_spans() + assert len(spans) >= 1 + s = spans[-1] + assert s.attributes.get("stream.call_cid") == "default:cid123" diff --git a/uv.lock b/uv.lock index f09e4180..229d4c4a 100644 --- a/uv.lock +++ b/uv.lock @@ -645,6 +645,10 @@ dependencies = [ openai-realtime = [ { name = "openai", extra = ["realtime"] }, ] +telemetry = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, +] webrtc = [ { name = "aiohttp" }, { name = "aiortc" }, @@ -667,6 +671,7 @@ webrtc = [ dev = [ { name = "grpcio-tools" }, { name = "mypy-protobuf" }, + { name = "opentelemetry-exporter-otlp" }, { name = "pre-commit" }, { name = "pytest" }, { name = "pytest-asyncio" }, @@ -686,6 +691,8 @@ requires-dist = [ { name = "numpy", marker = "extra == 'webrtc'", specifier = ">=2.2.6" }, { name = "numpy", marker = "extra == 'webrtc'", specifier = ">=2.2.6,<2.3" }, { name = "openai", extras = ["realtime"], marker = "extra == 'openai-realtime'", specifier = ">=1.93.0" }, + { name = "opentelemetry-api", marker = "extra == 'telemetry'", specifier = ">=1.26.0" }, + { name = "opentelemetry-sdk", marker = "extra == 'telemetry'", specifier = ">=1.26.0" }, { name = "ping3", marker = "extra == 'webrtc'", specifier = ">=4.0.8" }, { name = "protobuf", marker = "extra == 'webrtc'", specifier = ">=4.25.1" }, { name = "protobuf", marker = "extra == 'webrtc'", specifier = ">=6.31.1" }, @@ -707,12 +714,13 @@ requires-dist = [ { name = "websockets", marker = "extra == 'webrtc'", specifier = ">=15.0.1" }, { name = "websockets", marker = "extra == 'webrtc'", specifier = ">=15.0.1,<16" }, ] -provides-extras = ["openai-realtime", "webrtc"] +provides-extras = ["openai-realtime", "telemetry", "webrtc"] [package.metadata.requires-dev] dev = [ { name = "grpcio-tools", specifier = ">=1.73.1" }, { name = "mypy-protobuf", specifier = "==3.5.0" }, + { name = "opentelemetry-exporter-otlp", specifier = ">=1.37.0" }, { name = "pre-commit", specifier = ">=4.2.0" }, { name = "pytest", specifier = ">=8.4.1" }, { name = "pytest-asyncio", specifier = ">=1.0.0" }, @@ -758,6 +766,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fd/3c/2a19a60a473de48717b4efb19398c3f914795b64a96cf3fbe82588044f78/google_crc32c-1.7.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6efb97eb4369d52593ad6f75e7e10d053cf00c48983f7a973105bc70b0ac4d82", size = 28048 }, ] +[[package]] +name = "googleapis-common-protos" +version = "1.70.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/24/33db22342cf4a2ea27c9955e6713140fedd51e8b141b5ce5260897020f1a/googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257", size = 145903 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/f1/62a193f0227cf15a920390abe675f386dec35f7ae3ffe6da582d3ade42c7/googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8", size = 294530 }, +] + [[package]] name = "grpcio" version = "1.74.0" @@ -923,6 +943,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9c/1f/19ebc343cc71a7ffa78f17018535adc5cbdd87afb31d7c34874680148b32/ifaddr-0.2.0-py3-none-any.whl", hash = "sha256:085e0305cfe6f16ab12d72e2024030f5d52674afad6911bb1eee207177b8a748", size = 12314 }, ] +[[package]] +name = "importlib-metadata" +version = "8.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656 }, +] + [[package]] name = "iniconfig" version = "2.1.0" @@ -1466,6 +1498,119 @@ realtime = [ { name = "websockets" }, ] +[[package]] +name = "opentelemetry-api" +version = "1.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/04/05040d7ce33a907a2a02257e601992f0cdf11c73b33f13c4492bf6c3d6d5/opentelemetry_api-1.37.0.tar.gz", hash = "sha256:540735b120355bd5112738ea53621f8d5edb35ebcd6fe21ada3ab1c61d1cd9a7", size = 64923 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/48/28ed9e55dcf2f453128df738210a980e09f4e468a456fa3c763dbc8be70a/opentelemetry_api-1.37.0-py3-none-any.whl", hash = "sha256:accf2024d3e89faec14302213bc39550ec0f4095d1cf5ca688e1bfb1c8612f47", size = 65732 }, +] + +[[package]] +name = "opentelemetry-exporter-otlp" +version = "1.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-exporter-otlp-proto-grpc" }, + { name = "opentelemetry-exporter-otlp-proto-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/df/47fde1de15a3d5ad410e98710fac60cd3d509df5dc7ec1359b71d6bf7e70/opentelemetry_exporter_otlp-1.37.0.tar.gz", hash = "sha256:f85b1929dd0d750751cc9159376fb05aa88bb7a08b6cdbf84edb0054d93e9f26", size = 6145 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/23/7e35e41111e3834d918e414eca41555d585e8860c9149507298bb3b9b061/opentelemetry_exporter_otlp-1.37.0-py3-none-any.whl", hash = "sha256:bd44592c6bc7fc3e5c0a9b60f2ee813c84c2800c449e59504ab93f356cc450fc", size = 7019 }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-proto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/6c/10018cbcc1e6fff23aac67d7fd977c3d692dbe5f9ef9bb4db5c1268726cc/opentelemetry_exporter_otlp_proto_common-1.37.0.tar.gz", hash = "sha256:c87a1bdd9f41fdc408d9cc9367bb53f8d2602829659f2b90be9f9d79d0bfe62c", size = 20430 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/13/b4ef09837409a777f3c0af2a5b4ba9b7af34872bc43609dda0c209e4060d/opentelemetry_exporter_otlp_proto_common-1.37.0-py3-none-any.whl", hash = "sha256:53038428449c559b0c564b8d718df3314da387109c4d36bd1b94c9a641b0292e", size = 18359 }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-grpc" +version = "1.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "grpcio" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-common" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/11/4ad0979d0bb13ae5a845214e97c8d42da43980034c30d6f72d8e0ebe580e/opentelemetry_exporter_otlp_proto_grpc-1.37.0.tar.gz", hash = "sha256:f55bcb9fc848ce05ad3dd954058bc7b126624d22c4d9e958da24d8537763bec5", size = 24465 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/17/46630b74751031a658706bef23ac99cdc2953cd3b2d28ec90590a0766b3e/opentelemetry_exporter_otlp_proto_grpc-1.37.0-py3-none-any.whl", hash = "sha256:aee5104835bf7993b7ddaaf380b6467472abaedb1f1dbfcc54a52a7d781a3890", size = 19305 }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-http" +version = "1.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-common" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/e3/6e320aeb24f951449e73867e53c55542bebbaf24faeee7623ef677d66736/opentelemetry_exporter_otlp_proto_http-1.37.0.tar.gz", hash = "sha256:e52e8600f1720d6de298419a802108a8f5afa63c96809ff83becb03f874e44ac", size = 17281 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/e9/70d74a664d83976556cec395d6bfedd9b85ec1498b778367d5f93e373397/opentelemetry_exporter_otlp_proto_http-1.37.0-py3-none-any.whl", hash = "sha256:54c42b39945a6cc9d9a2a33decb876eabb9547e0dcb49df090122773447f1aef", size = 19576 }, +] + +[[package]] +name = "opentelemetry-proto" +version = "1.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dd/ea/a75f36b463a36f3c5a10c0b5292c58b31dbdde74f6f905d3d0ab2313987b/opentelemetry_proto-1.37.0.tar.gz", hash = "sha256:30f5c494faf66f77faeaefa35ed4443c5edb3b0aa46dad073ed7210e1a789538", size = 46151 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/25/f89ea66c59bd7687e218361826c969443c4fa15dfe89733f3bf1e2a9e971/opentelemetry_proto-1.37.0-py3-none-any.whl", hash = "sha256:8ed8c066ae8828bbf0c39229979bdf583a126981142378a9cbe9d6fd5701c6e2", size = 72534 }, +] + +[[package]] +name = "opentelemetry-sdk" +version = "1.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/62/2e0ca80d7fe94f0b193135375da92c640d15fe81f636658d2acf373086bc/opentelemetry_sdk-1.37.0.tar.gz", hash = "sha256:cc8e089c10953ded765b5ab5669b198bbe0af1b3f89f1007d19acd32dc46dda5", size = 170404 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/62/9f4ad6a54126fb00f7ed4bb5034964c6e4f00fcd5a905e115bd22707e20d/opentelemetry_sdk-1.37.0-py3-none-any.whl", hash = "sha256:8f3c3c22063e52475c5dbced7209495c2c16723d016d39287dfc215d1771257c", size = 131941 }, +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.58b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/1b/90701d91e6300d9f2fb352153fb1721ed99ed1f6ea14fa992c756016e63a/opentelemetry_semantic_conventions-0.58b0.tar.gz", hash = "sha256:6bd46f51264279c433755767bb44ad00f1c9e2367e1b42af563372c5a6fa0c25", size = 129867 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/90/68152b7465f50285d3ce2481b3aec2f82822e3f52e5152eeeaf516bab841/opentelemetry_semantic_conventions-0.58b0-py3-none-any.whl", hash = "sha256:5564905ab1458b96684db1340232729fce3b5375a06e140e8904c78e4f815b28", size = 207954 }, +] + [[package]] name = "packaging" version = "25.0" @@ -2487,3 +2632,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/c3/b2e9f38bc3e11191981d57ea08cab2166e74ea770024a646617c9cddd9f6/yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f", size = 93003 }, { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542 }, ] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276 }, +] From 8c79b92178713472f8837de5af39678f33367df8 Mon Sep 17 00:00:00 2001 From: Tommaso Barbugli Date: Fri, 26 Sep 2025 17:41:11 +0200 Subject: [PATCH 02/12] codegen done --- getstream/chat/async_channel.py | 8 +- getstream/chat/async_rest_client.py | 154 +++++------ getstream/chat/channel.py | 7 +- getstream/chat/rest_client.py | 154 +++++------ getstream/common/async_rest_client.py | 133 +++++----- getstream/common/rest_client.py | 133 +++++----- getstream/models/__init__.py | 301 +++++++++++++++------- getstream/moderation/async_rest_client.py | 45 ++-- getstream/moderation/rest_client.py | 45 ++-- getstream/stream.py | 8 +- getstream/video/async_call.py | 4 +- getstream/video/async_rest_client.py | 120 +++++---- getstream/video/call.py | 4 +- getstream/video/rest_client.py | 120 +++++---- 14 files changed, 731 insertions(+), 505 deletions(-) diff --git a/getstream/chat/async_channel.py b/getstream/chat/async_channel.py index 8749046e..3da906df 100644 --- a/getstream/chat/async_channel.py +++ b/getstream/chat/async_channel.py @@ -1,7 +1,7 @@ # Code generated by GetStream internal OpenAPI code generator. DO NOT EDIT. +from getstream.common.telemetry import attach_channel_cid_async from getstream.models import * from getstream.stream_response import StreamResponse -from getstream.common.telemetry import attach_channel_cid_async class Channel: @@ -227,6 +227,7 @@ async def send_message( self._sync_from_response(response.data) return response + @attach_channel_cid_async async def get_many_messages( self, ids: List[str] ) -> StreamResponse[GetManyMessagesResponse]: @@ -236,6 +237,7 @@ async def get_many_messages( self._sync_from_response(response.data) return response + @attach_channel_cid_async async def get_or_create( self, hide_for_creator: Optional[bool] = None, @@ -260,6 +262,7 @@ async def get_or_create( self._sync_from_response(response.data) return response + @attach_channel_cid_async async def mark_read( self, message_id: Optional[str] = None, @@ -278,6 +281,7 @@ async def mark_read( self._sync_from_response(response.data) return response + @attach_channel_cid_async async def show( self, user_id: Optional[str] = None, user: Optional[UserRequest] = None ) -> StreamResponse[ShowChannelResponse]: @@ -287,6 +291,7 @@ async def show( self._sync_from_response(response.data) return response + @attach_channel_cid_async async def truncate( self, hard_delete: Optional[bool] = None, @@ -311,6 +316,7 @@ async def truncate( self._sync_from_response(response.data) return response + @attach_channel_cid_async async def mark_unread( self, message_id: Optional[str] = None, diff --git a/getstream/chat/async_rest_client.py b/getstream/chat/async_rest_client.py index 818bfc8b..9b95ddf0 100644 --- a/getstream/chat/async_rest_client.py +++ b/getstream/chat/async_rest_client.py @@ -1,5 +1,6 @@ # Code generated by GetStream internal OpenAPI code generator. DO NOT EDIT. from getstream.base import AsyncBaseClient +from getstream.common import telemetry from getstream.models import * from getstream.stream_response import StreamResponse from getstream.utils import build_query_param, build_body_dict @@ -21,6 +22,7 @@ def __init__(self, api_key: str, base_url: str, timeout: float, token: str): token=token, ) + @telemetry.operation_name("getstream.api.chat.query_campaigns") async def query_campaigns( self, limit: Optional[int] = None, @@ -38,11 +40,11 @@ async def query_campaigns( sort=sort, filter=filter, ) - return await self.post( "/api/v2/chat/campaigns/query", QueryCampaignsResponse, json=json ) + @telemetry.operation_name("getstream.api.chat.get_campaign") async def get_campaign( self, id: str, @@ -54,7 +56,6 @@ async def get_campaign( path_params = { "id": id, } - return await self.get( "/api/v2/chat/campaigns/{id}", GetCampaignResponse, @@ -62,6 +63,7 @@ async def get_campaign( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.start_campaign") async def start_campaign( self, id: str, @@ -72,7 +74,6 @@ async def start_campaign( "id": id, } json = build_body_dict(scheduled_for=scheduled_for, stop_at=stop_at) - return await self.post( "/api/v2/chat/campaigns/{id}/start", StartCampaignResponse, @@ -80,6 +81,7 @@ async def start_campaign( json=json, ) + @telemetry.operation_name("getstream.api.chat.schedule_campaign") async def schedule_campaign( self, id: str, @@ -88,7 +90,6 @@ async def schedule_campaign( "id": id, } json = build_body_dict() - return await self.post( "/api/v2/chat/campaigns/{id}/stop", CampaignResponse, @@ -96,6 +97,7 @@ async def schedule_campaign( json=json, ) + @telemetry.operation_name("getstream.api.chat.query_channels") async def query_channels( self, limit: Optional[int] = None, @@ -119,20 +121,20 @@ async def query_channels( filter_conditions=filter_conditions, user=user, ) - return await self.post( "/api/v2/chat/channels", QueryChannelsResponse, json=json ) + @telemetry.operation_name("getstream.api.chat.delete_channels") async def delete_channels( self, cids: List[str], hard_delete: Optional[bool] = None ) -> StreamResponse[DeleteChannelsResponse]: json = build_body_dict(cids=cids, hard_delete=hard_delete) - return await self.post( "/api/v2/chat/channels/delete", DeleteChannelsResponse, json=json ) + @telemetry.operation_name("getstream.api.chat.mark_channels_read") async def mark_channels_read( self, user_id: Optional[str] = None, @@ -142,11 +144,11 @@ async def mark_channels_read( json = build_body_dict( user_id=user_id, read_by_channel=read_by_channel, user=user ) - return await self.post( "/api/v2/chat/channels/read", MarkReadResponse, json=json ) + @telemetry.operation_name("getstream.api.chat.get_or_create_distinct_channel") async def get_or_create_distinct_channel( self, type: str, @@ -170,7 +172,6 @@ async def get_or_create_distinct_channel( messages=messages, watchers=watchers, ) - return await self.post( "/api/v2/chat/channels/{type}/query", ChannelStateResponse, @@ -178,6 +179,7 @@ async def get_or_create_distinct_channel( json=json, ) + @telemetry.operation_name("getstream.api.chat.delete_channel") async def delete_channel( self, type: str, id: str, hard_delete: Optional[bool] = None ) -> StreamResponse[DeleteChannelResponse]: @@ -186,7 +188,6 @@ async def delete_channel( "type": type, "id": id, } - return await self.delete( "/api/v2/chat/channels/{type}/{id}", DeleteChannelResponse, @@ -194,6 +195,7 @@ async def delete_channel( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.update_channel_partial") async def update_channel_partial( self, type: str, @@ -208,7 +210,6 @@ async def update_channel_partial( "id": id, } json = build_body_dict(user_id=user_id, unset=unset, set=set, user=user) - return await self.patch( "/api/v2/chat/channels/{type}/{id}", UpdateChannelPartialResponse, @@ -216,6 +217,7 @@ async def update_channel_partial( json=json, ) + @telemetry.operation_name("getstream.api.chat.update_channel") async def update_channel( self, type: str, @@ -257,7 +259,6 @@ async def update_channel( message=message, user=user, ) - return await self.post( "/api/v2/chat/channels/{type}/{id}", UpdateChannelResponse, @@ -265,6 +266,7 @@ async def update_channel( json=json, ) + @telemetry.operation_name("getstream.api.chat.delete_draft") async def delete_draft( self, type: str, @@ -277,7 +279,6 @@ async def delete_draft( "type": type, "id": id, } - return await self.delete( "/api/v2/chat/channels/{type}/{id}/draft", Response, @@ -285,6 +286,7 @@ async def delete_draft( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.get_draft") async def get_draft( self, type: str, @@ -297,7 +299,6 @@ async def get_draft( "type": type, "id": id, } - return await self.get( "/api/v2/chat/channels/{type}/{id}/draft", GetDraftResponse, @@ -305,6 +306,7 @@ async def get_draft( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.send_event") async def send_event( self, type: str, id: str, event: EventRequest ) -> StreamResponse[EventResponse]: @@ -313,7 +315,6 @@ async def send_event( "id": id, } json = build_body_dict(event=event) - return await self.post( "/api/v2/chat/channels/{type}/{id}/event", EventResponse, @@ -321,6 +322,7 @@ async def send_event( json=json, ) + @telemetry.operation_name("getstream.api.chat.delete_file") async def delete_file( self, type: str, id: str, url: Optional[str] = None ) -> StreamResponse[Response]: @@ -329,7 +331,6 @@ async def delete_file( "type": type, "id": id, } - return await self.delete( "/api/v2/chat/channels/{type}/{id}/file", Response, @@ -337,6 +338,7 @@ async def delete_file( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.upload_file") async def upload_file( self, type: str, @@ -349,7 +351,6 @@ async def upload_file( "id": id, } json = build_body_dict(file=file, user=user) - return await self.post( "/api/v2/chat/channels/{type}/{id}/file", FileUploadResponse, @@ -357,6 +358,7 @@ async def upload_file( json=json, ) + @telemetry.operation_name("getstream.api.chat.hide_channel") async def hide_channel( self, type: str, @@ -370,7 +372,6 @@ async def hide_channel( "id": id, } json = build_body_dict(clear_history=clear_history, user_id=user_id, user=user) - return await self.post( "/api/v2/chat/channels/{type}/{id}/hide", HideChannelResponse, @@ -378,6 +379,7 @@ async def hide_channel( json=json, ) + @telemetry.operation_name("getstream.api.chat.delete_image") async def delete_image( self, type: str, id: str, url: Optional[str] = None ) -> StreamResponse[Response]: @@ -386,7 +388,6 @@ async def delete_image( "type": type, "id": id, } - return await self.delete( "/api/v2/chat/channels/{type}/{id}/image", Response, @@ -394,6 +395,7 @@ async def delete_image( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.upload_image") async def upload_image( self, type: str, @@ -407,7 +409,6 @@ async def upload_image( "id": id, } json = build_body_dict(file=file, upload_sizes=upload_sizes, user=user) - return await self.post( "/api/v2/chat/channels/{type}/{id}/image", ImageUploadResponse, @@ -415,6 +416,7 @@ async def upload_image( json=json, ) + @telemetry.operation_name("getstream.api.chat.update_member_partial") async def update_member_partial( self, type: str, @@ -429,7 +431,6 @@ async def update_member_partial( "id": id, } json = build_body_dict(unset=unset, set=set) - return await self.patch( "/api/v2/chat/channels/{type}/{id}/member", UpdateMemberPartialResponse, @@ -438,6 +439,7 @@ async def update_member_partial( json=json, ) + @telemetry.operation_name("getstream.api.chat.send_message") async def send_message( self, type: str, @@ -463,7 +465,6 @@ async def send_message( skip_push=skip_push, pending_message_metadata=pending_message_metadata, ) - return await self.post( "/api/v2/chat/channels/{type}/{id}/message", SendMessageResponse, @@ -471,6 +472,7 @@ async def send_message( json=json, ) + @telemetry.operation_name("getstream.api.chat.get_many_messages") async def get_many_messages( self, type: str, id: str, ids: List[str] ) -> StreamResponse[GetManyMessagesResponse]: @@ -479,7 +481,6 @@ async def get_many_messages( "type": type, "id": id, } - return await self.get( "/api/v2/chat/channels/{type}/{id}/messages", GetManyMessagesResponse, @@ -487,6 +488,7 @@ async def get_many_messages( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.get_or_create_channel") async def get_or_create_channel( self, type: str, @@ -512,7 +514,6 @@ async def get_or_create_channel( messages=messages, watchers=watchers, ) - return await self.post( "/api/v2/chat/channels/{type}/{id}/query", ChannelStateResponse, @@ -520,6 +521,7 @@ async def get_or_create_channel( json=json, ) + @telemetry.operation_name("getstream.api.chat.mark_read") async def mark_read( self, type: str, @@ -536,7 +538,6 @@ async def mark_read( json = build_body_dict( message_id=message_id, thread_id=thread_id, user_id=user_id, user=user ) - return await self.post( "/api/v2/chat/channels/{type}/{id}/read", MarkReadResponse, @@ -544,6 +545,7 @@ async def mark_read( json=json, ) + @telemetry.operation_name("getstream.api.chat.show_channel") async def show_channel( self, type: str, @@ -556,7 +558,6 @@ async def show_channel( "id": id, } json = build_body_dict(user_id=user_id, user=user) - return await self.post( "/api/v2/chat/channels/{type}/{id}/show", ShowChannelResponse, @@ -564,6 +565,7 @@ async def show_channel( json=json, ) + @telemetry.operation_name("getstream.api.chat.truncate_channel") async def truncate_channel( self, type: str, @@ -589,7 +591,6 @@ async def truncate_channel( message=message, user=user, ) - return await self.post( "/api/v2/chat/channels/{type}/{id}/truncate", TruncateChannelResponse, @@ -597,6 +598,7 @@ async def truncate_channel( json=json, ) + @telemetry.operation_name("getstream.api.chat.mark_unread") async def mark_unread( self, type: str, @@ -613,7 +615,6 @@ async def mark_unread( json = build_body_dict( message_id=message_id, thread_id=thread_id, user_id=user_id, user=user ) - return await self.post( "/api/v2/chat/channels/{type}/{id}/unread", Response, @@ -621,9 +622,11 @@ async def mark_unread( json=json, ) + @telemetry.operation_name("getstream.api.chat.list_channel_types") async def list_channel_types(self) -> StreamResponse[ListChannelTypesResponse]: return await self.get("/api/v2/chat/channeltypes", ListChannelTypesResponse) + @telemetry.operation_name("getstream.api.chat.create_channel_type") async def create_channel_type( self, automod: str, @@ -687,33 +690,33 @@ async def create_channel_type( permissions=permissions, grants=grants, ) - return await self.post( "/api/v2/chat/channeltypes", CreateChannelTypeResponse, json=json ) + @telemetry.operation_name("getstream.api.chat.delete_channel_type") async def delete_channel_type(self, name: str) -> StreamResponse[Response]: path_params = { "name": name, } - return await self.delete( "/api/v2/chat/channeltypes/{name}", Response, path_params=path_params ) + @telemetry.operation_name("getstream.api.chat.get_channel_type") async def get_channel_type( self, name: str ) -> StreamResponse[GetChannelTypeResponse]: path_params = { "name": name, } - return await self.get( "/api/v2/chat/channeltypes/{name}", GetChannelTypeResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.update_channel_type") async def update_channel_type( self, name: str, @@ -787,7 +790,6 @@ async def update_channel_type( automod_thresholds=automod_thresholds, grants=grants, ) - return await self.put( "/api/v2/chat/channeltypes/{name}", UpdateChannelTypeResponse, @@ -795,9 +797,11 @@ async def update_channel_type( json=json, ) + @telemetry.operation_name("getstream.api.chat.list_commands") async def list_commands(self) -> StreamResponse[ListCommandsResponse]: return await self.get("/api/v2/chat/commands", ListCommandsResponse) + @telemetry.operation_name("getstream.api.chat.create_command") async def create_command( self, description: str, @@ -806,31 +810,31 @@ async def create_command( set: Optional[str] = None, ) -> StreamResponse[CreateCommandResponse]: json = build_body_dict(description=description, name=name, args=args, set=set) - return await self.post( "/api/v2/chat/commands", CreateCommandResponse, json=json ) + @telemetry.operation_name("getstream.api.chat.delete_command") async def delete_command(self, name: str) -> StreamResponse[DeleteCommandResponse]: path_params = { "name": name, } - return await self.delete( "/api/v2/chat/commands/{name}", DeleteCommandResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.get_command") async def get_command(self, name: str) -> StreamResponse[GetCommandResponse]: path_params = { "name": name, } - return await self.get( "/api/v2/chat/commands/{name}", GetCommandResponse, path_params=path_params ) + @telemetry.operation_name("getstream.api.chat.update_command") async def update_command( self, name: str, @@ -842,7 +846,6 @@ async def update_command( "name": name, } json = build_body_dict(description=description, args=args, set=set) - return await self.put( "/api/v2/chat/commands/{name}", UpdateCommandResponse, @@ -850,6 +853,7 @@ async def update_command( json=json, ) + @telemetry.operation_name("getstream.api.chat.query_drafts") async def query_drafts( self, limit: Optional[int] = None, @@ -869,11 +873,11 @@ async def query_drafts( filter=filter, user=user, ) - return await self.post( "/api/v2/chat/drafts/query", QueryDraftsResponse, json=json ) + @telemetry.operation_name("getstream.api.chat.export_channels") async def export_channels( self, channels: List[ChannelExport], @@ -891,20 +895,20 @@ async def export_channels( include_truncated_messages=include_truncated_messages, version=version, ) - return await self.post( "/api/v2/chat/export_channels", ExportChannelsResponse, json=json ) + @telemetry.operation_name("getstream.api.chat.query_members") async def query_members( self, payload: Optional[QueryMembersPayload] = None ) -> StreamResponse[MembersResponse]: query_params = build_query_param(payload=payload) - return await self.get( "/api/v2/chat/members", MembersResponse, query_params=query_params ) + @telemetry.operation_name("getstream.api.chat.query_message_history") async def query_message_history( self, filter: Dict[str, object], @@ -916,11 +920,11 @@ async def query_message_history( json = build_body_dict( filter=filter, limit=limit, next=next, prev=prev, sort=sort ) - return await self.post( "/api/v2/chat/messages/history", QueryMessageHistoryResponse, json=json ) + @telemetry.operation_name("getstream.api.chat.delete_message") async def delete_message( self, id: str, @@ -934,7 +938,6 @@ async def delete_message( path_params = { "id": id, } - return await self.delete( "/api/v2/chat/messages/{id}", DeleteMessageResponse, @@ -942,6 +945,7 @@ async def delete_message( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.get_message") async def get_message( self, id: str, show_deleted_message: Optional[bool] = None ) -> StreamResponse[GetMessageResponse]: @@ -949,7 +953,6 @@ async def get_message( path_params = { "id": id, } - return await self.get( "/api/v2/chat/messages/{id}", GetMessageResponse, @@ -957,6 +960,7 @@ async def get_message( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.update_message") async def update_message( self, id: str, @@ -970,7 +974,6 @@ async def update_message( json = build_body_dict( message=message, skip_enrich_url=skip_enrich_url, skip_push=skip_push ) - return await self.post( "/api/v2/chat/messages/{id}", UpdateMessageResponse, @@ -978,6 +981,7 @@ async def update_message( json=json, ) + @telemetry.operation_name("getstream.api.chat.update_message_partial") async def update_message_partial( self, id: str, @@ -997,7 +1001,6 @@ async def update_message_partial( set=set, user=user, ) - return await self.put( "/api/v2/chat/messages/{id}", UpdateMessagePartialResponse, @@ -1005,6 +1008,7 @@ async def update_message_partial( json=json, ) + @telemetry.operation_name("getstream.api.chat.run_message_action") async def run_message_action( self, id: str, @@ -1016,7 +1020,6 @@ async def run_message_action( "id": id, } json = build_body_dict(form_data=form_data, user_id=user_id, user=user) - return await self.post( "/api/v2/chat/messages/{id}/action", MessageResponse, @@ -1024,6 +1027,7 @@ async def run_message_action( json=json, ) + @telemetry.operation_name("getstream.api.chat.commit_message") async def commit_message( self, id: str, @@ -1032,7 +1036,6 @@ async def commit_message( "id": id, } json = build_body_dict() - return await self.post( "/api/v2/chat/messages/{id}/commit", MessageResponse, @@ -1040,6 +1043,7 @@ async def commit_message( json=json, ) + @telemetry.operation_name("getstream.api.chat.ephemeral_message_update") async def ephemeral_message_update( self, id: str, @@ -1059,7 +1063,6 @@ async def ephemeral_message_update( set=set, user=user, ) - return await self.patch( "/api/v2/chat/messages/{id}/ephemeral", UpdateMessagePartialResponse, @@ -1067,6 +1070,7 @@ async def ephemeral_message_update( json=json, ) + @telemetry.operation_name("getstream.api.chat.send_reaction") async def send_reaction( self, id: str, @@ -1080,7 +1084,6 @@ async def send_reaction( json = build_body_dict( reaction=reaction, enforce_unique=enforce_unique, skip_push=skip_push ) - return await self.post( "/api/v2/chat/messages/{id}/reaction", SendReactionResponse, @@ -1088,6 +1091,7 @@ async def send_reaction( json=json, ) + @telemetry.operation_name("getstream.api.chat.delete_reaction") async def delete_reaction( self, id: str, type: str, user_id: Optional[str] = None ) -> StreamResponse[DeleteReactionResponse]: @@ -1096,7 +1100,6 @@ async def delete_reaction( "id": id, "type": type, } - return await self.delete( "/api/v2/chat/messages/{id}/reaction/{type}", DeleteReactionResponse, @@ -1104,6 +1107,7 @@ async def delete_reaction( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.get_reactions") async def get_reactions( self, id: str, limit: Optional[int] = None, offset: Optional[int] = None ) -> StreamResponse[GetReactionsResponse]: @@ -1111,7 +1115,6 @@ async def get_reactions( path_params = { "id": id, } - return await self.get( "/api/v2/chat/messages/{id}/reactions", GetReactionsResponse, @@ -1119,6 +1122,7 @@ async def get_reactions( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.query_reactions") async def query_reactions( self, id: str, @@ -1142,7 +1146,6 @@ async def query_reactions( filter=filter, user=user, ) - return await self.post( "/api/v2/chat/messages/{id}/reactions", QueryReactionsResponse, @@ -1150,6 +1153,7 @@ async def query_reactions( json=json, ) + @telemetry.operation_name("getstream.api.chat.translate_message") async def translate_message( self, id: str, language: str ) -> StreamResponse[MessageResponse]: @@ -1157,7 +1161,6 @@ async def translate_message( "id": id, } json = build_body_dict(language=language) - return await self.post( "/api/v2/chat/messages/{id}/translate", MessageResponse, @@ -1165,6 +1168,7 @@ async def translate_message( json=json, ) + @telemetry.operation_name("getstream.api.chat.undelete_message") async def undelete_message( self, id: str, @@ -1178,7 +1182,6 @@ async def undelete_message( json = build_body_dict( message=message, skip_enrich_url=skip_enrich_url, skip_push=skip_push ) - return await self.post( "/api/v2/chat/messages/{id}/undelete", UpdateMessageResponse, @@ -1186,6 +1189,7 @@ async def undelete_message( json=json, ) + @telemetry.operation_name("getstream.api.chat.cast_poll_vote") async def cast_poll_vote( self, message_id: str, @@ -1199,7 +1203,6 @@ async def cast_poll_vote( "poll_id": poll_id, } json = build_body_dict(user_id=user_id, user=user, vote=vote) - return await self.post( "/api/v2/chat/messages/{message_id}/polls/{poll_id}/vote", PollVoteResponse, @@ -1207,6 +1210,7 @@ async def cast_poll_vote( json=json, ) + @telemetry.operation_name("getstream.api.chat.delete_poll_vote") async def delete_poll_vote( self, message_id: str, poll_id: str, vote_id: str, user_id: Optional[str] = None ) -> StreamResponse[PollVoteResponse]: @@ -1216,7 +1220,6 @@ async def delete_poll_vote( "poll_id": poll_id, "vote_id": vote_id, } - return await self.delete( "/api/v2/chat/messages/{message_id}/polls/{poll_id}/vote/{vote_id}", PollVoteResponse, @@ -1224,6 +1227,7 @@ async def delete_poll_vote( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.delete_reminder") async def delete_reminder( self, message_id: str, user_id: Optional[str] = None ) -> StreamResponse[DeleteReminderResponse]: @@ -1231,7 +1235,6 @@ async def delete_reminder( path_params = { "message_id": message_id, } - return await self.delete( "/api/v2/chat/messages/{message_id}/reminders", DeleteReminderResponse, @@ -1239,6 +1242,7 @@ async def delete_reminder( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.update_reminder") async def update_reminder( self, message_id: str, @@ -1250,7 +1254,6 @@ async def update_reminder( "message_id": message_id, } json = build_body_dict(remind_at=remind_at, user_id=user_id, user=user) - return await self.patch( "/api/v2/chat/messages/{message_id}/reminders", UpdateReminderResponse, @@ -1258,6 +1261,7 @@ async def update_reminder( json=json, ) + @telemetry.operation_name("getstream.api.chat.create_reminder") async def create_reminder( self, message_id: str, @@ -1269,7 +1273,6 @@ async def create_reminder( "message_id": message_id, } json = build_body_dict(remind_at=remind_at, user_id=user_id, user=user) - return await self.post( "/api/v2/chat/messages/{message_id}/reminders", ReminderResponseData, @@ -1277,6 +1280,7 @@ async def create_reminder( json=json, ) + @telemetry.operation_name("getstream.api.chat.get_replies") async def get_replies( self, parent_id: str, @@ -1312,7 +1316,6 @@ async def get_replies( path_params = { "parent_id": parent_id, } - return await self.get( "/api/v2/chat/messages/{parent_id}/replies", GetRepliesResponse, @@ -1320,17 +1323,18 @@ async def get_replies( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.query_message_flags") async def query_message_flags( self, payload: Optional[QueryMessageFlagsPayload] = None ) -> StreamResponse[QueryMessageFlagsResponse]: query_params = build_query_param(payload=payload) - return await self.get( "/api/v2/chat/moderation/flags/message", QueryMessageFlagsResponse, query_params=query_params, ) + @telemetry.operation_name("getstream.api.chat.mute_channel") async def mute_channel( self, expiration: Optional[int] = None, @@ -1341,11 +1345,11 @@ async def mute_channel( json = build_body_dict( expiration=expiration, user_id=user_id, channel_cids=channel_cids, user=user ) - return await self.post( "/api/v2/chat/moderation/mute/channel", MuteChannelResponse, json=json ) + @telemetry.operation_name("getstream.api.chat.unmute_channel") async def unmute_channel( self, expiration: Optional[int] = None, @@ -1356,22 +1360,22 @@ async def unmute_channel( json = build_body_dict( expiration=expiration, user_id=user_id, channel_cids=channel_cids, user=user ) - return await self.post( "/api/v2/chat/moderation/unmute/channel", UnmuteResponse, json=json ) + @telemetry.operation_name("getstream.api.chat.query_banned_users") async def query_banned_users( self, payload: Optional[QueryBannedUsersPayload] = None ) -> StreamResponse[QueryBannedUsersResponse]: query_params = build_query_param(payload=payload) - return await self.get( "/api/v2/chat/query_banned_users", QueryBannedUsersResponse, query_params=query_params, ) + @telemetry.operation_name("getstream.api.chat.query_reminders") async def query_reminders( self, limit: Optional[int] = None, @@ -1391,20 +1395,20 @@ async def query_reminders( filter=filter, user=user, ) - return await self.post( "/api/v2/chat/reminders/query", QueryRemindersResponse, json=json ) + @telemetry.operation_name("getstream.api.chat.search") async def search( self, payload: Optional[SearchPayload] = None ) -> StreamResponse[SearchResponse]: query_params = build_query_param(payload=payload) - return await self.get( "/api/v2/chat/search", SearchResponse, query_params=query_params ) + @telemetry.operation_name("getstream.api.chat.query_segments") async def query_segments( self, filter: Dict[str, object], @@ -1416,29 +1420,29 @@ async def query_segments( json = build_body_dict( filter=filter, limit=limit, next=next, prev=prev, sort=sort ) - return await self.post( "/api/v2/chat/segments/query", QuerySegmentsResponse, json=json ) + @telemetry.operation_name("getstream.api.chat.delete_segment") async def delete_segment(self, id: str) -> StreamResponse[Response]: path_params = { "id": id, } - return await self.delete( "/api/v2/chat/segments/{id}", Response, path_params=path_params ) + @telemetry.operation_name("getstream.api.chat.get_segment") async def get_segment(self, id: str) -> StreamResponse[GetSegmentResponse]: path_params = { "id": id, } - return await self.get( "/api/v2/chat/segments/{id}", GetSegmentResponse, path_params=path_params ) + @telemetry.operation_name("getstream.api.chat.delete_segment_targets") async def delete_segment_targets( self, id: str, target_ids: List[str] ) -> StreamResponse[Response]: @@ -1446,7 +1450,6 @@ async def delete_segment_targets( "id": id, } json = build_body_dict(target_ids=target_ids) - return await self.post( "/api/v2/chat/segments/{id}/deletetargets", Response, @@ -1454,6 +1457,7 @@ async def delete_segment_targets( json=json, ) + @telemetry.operation_name("getstream.api.chat.segment_target_exists") async def segment_target_exists( self, id: str, target_id: str ) -> StreamResponse[Response]: @@ -1461,13 +1465,13 @@ async def segment_target_exists( "id": id, "target_id": target_id, } - return await self.get( "/api/v2/chat/segments/{id}/target/{target_id}", Response, path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.query_segment_targets") async def query_segment_targets( self, id: str, @@ -1483,7 +1487,6 @@ async def query_segment_targets( json = build_body_dict( limit=limit, next=next, prev=prev, sort=sort, filter=filter ) - return await self.post( "/api/v2/chat/segments/{id}/targets/query", QuerySegmentTargetsResponse, @@ -1491,6 +1494,7 @@ async def query_segment_targets( json=json, ) + @telemetry.operation_name("getstream.api.chat.query_threads") async def query_threads( self, limit: Optional[int] = None, @@ -1516,9 +1520,9 @@ async def query_threads( filter=filter, user=user, ) - return await self.post("/api/v2/chat/threads", QueryThreadsResponse, json=json) + @telemetry.operation_name("getstream.api.chat.get_thread") async def get_thread( self, message_id: str, @@ -1534,7 +1538,6 @@ async def get_thread( path_params = { "message_id": message_id, } - return await self.get( "/api/v2/chat/threads/{message_id}", GetThreadResponse, @@ -1542,6 +1545,7 @@ async def get_thread( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.update_thread_partial") async def update_thread_partial( self, message_id: str, @@ -1554,7 +1558,6 @@ async def update_thread_partial( "message_id": message_id, } json = build_body_dict(user_id=user_id, unset=unset, set=set, user=user) - return await self.patch( "/api/v2/chat/threads/{message_id}", UpdateThreadPartialResponse, @@ -1562,18 +1565,20 @@ async def update_thread_partial( json=json, ) + @telemetry.operation_name("getstream.api.chat.unread_counts") async def unread_counts(self) -> StreamResponse[WrappedUnreadCountsResponse]: return await self.get("/api/v2/chat/unread", WrappedUnreadCountsResponse) + @telemetry.operation_name("getstream.api.chat.unread_counts_batch") async def unread_counts_batch( self, user_ids: List[str] ) -> StreamResponse[UnreadCountsBatchResponse]: json = build_body_dict(user_ids=user_ids) - return await self.post( "/api/v2/chat/unread_batch", UnreadCountsBatchResponse, json=json ) + @telemetry.operation_name("getstream.api.chat.send_user_custom_event") async def send_user_custom_event( self, user_id: str, event: UserCustomEventRequest ) -> StreamResponse[Response]: @@ -1581,7 +1586,6 @@ async def send_user_custom_event( "user_id": user_id, } json = build_body_dict(event=event) - return await self.post( "/api/v2/chat/users/{user_id}/event", Response, diff --git a/getstream/chat/channel.py b/getstream/chat/channel.py index 80dbb5ac..1cd35e0b 100644 --- a/getstream/chat/channel.py +++ b/getstream/chat/channel.py @@ -1,7 +1,7 @@ # Code generated by GetStream internal OpenAPI code generator. DO NOT EDIT. +from getstream.common.telemetry import attach_channel_cid from getstream.models import * from getstream.stream_response import StreamResponse -from getstream.common.telemetry import attach_channel_cid class Channel: @@ -227,6 +227,7 @@ def send_message( self._sync_from_response(response.data) return response + @attach_channel_cid def get_many_messages( self, ids: List[str] ) -> StreamResponse[GetManyMessagesResponse]: @@ -261,6 +262,7 @@ def get_or_create( self._sync_from_response(response.data) return response + @attach_channel_cid def mark_read( self, message_id: Optional[str] = None, @@ -279,6 +281,7 @@ def mark_read( self._sync_from_response(response.data) return response + @attach_channel_cid def show( self, user_id: Optional[str] = None, user: Optional[UserRequest] = None ) -> StreamResponse[ShowChannelResponse]: @@ -288,6 +291,7 @@ def show( self._sync_from_response(response.data) return response + @attach_channel_cid def truncate( self, hard_delete: Optional[bool] = None, @@ -312,6 +316,7 @@ def truncate( self._sync_from_response(response.data) return response + @attach_channel_cid def mark_unread( self, message_id: Optional[str] = None, diff --git a/getstream/chat/rest_client.py b/getstream/chat/rest_client.py index ff561300..c371706c 100644 --- a/getstream/chat/rest_client.py +++ b/getstream/chat/rest_client.py @@ -1,5 +1,6 @@ # Code generated by GetStream internal OpenAPI code generator. DO NOT EDIT. from getstream.base import BaseClient +from getstream.common import telemetry from getstream.models import * from getstream.stream_response import StreamResponse from getstream.utils import build_query_param, build_body_dict @@ -21,6 +22,7 @@ def __init__(self, api_key: str, base_url: str, timeout: float, token: str): token=token, ) + @telemetry.operation_name("getstream.api.chat.query_campaigns") def query_campaigns( self, limit: Optional[int] = None, @@ -38,11 +40,11 @@ def query_campaigns( sort=sort, filter=filter, ) - return self.post( "/api/v2/chat/campaigns/query", QueryCampaignsResponse, json=json ) + @telemetry.operation_name("getstream.api.chat.get_campaign") def get_campaign( self, id: str, @@ -54,7 +56,6 @@ def get_campaign( path_params = { "id": id, } - return self.get( "/api/v2/chat/campaigns/{id}", GetCampaignResponse, @@ -62,6 +63,7 @@ def get_campaign( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.start_campaign") def start_campaign( self, id: str, @@ -72,7 +74,6 @@ def start_campaign( "id": id, } json = build_body_dict(scheduled_for=scheduled_for, stop_at=stop_at) - return self.post( "/api/v2/chat/campaigns/{id}/start", StartCampaignResponse, @@ -80,6 +81,7 @@ def start_campaign( json=json, ) + @telemetry.operation_name("getstream.api.chat.schedule_campaign") def schedule_campaign( self, id: str, @@ -88,7 +90,6 @@ def schedule_campaign( "id": id, } json = build_body_dict() - return self.post( "/api/v2/chat/campaigns/{id}/stop", CampaignResponse, @@ -96,6 +97,7 @@ def schedule_campaign( json=json, ) + @telemetry.operation_name("getstream.api.chat.query_channels") def query_channels( self, limit: Optional[int] = None, @@ -119,18 +121,18 @@ def query_channels( filter_conditions=filter_conditions, user=user, ) - return self.post("/api/v2/chat/channels", QueryChannelsResponse, json=json) + @telemetry.operation_name("getstream.api.chat.delete_channels") def delete_channels( self, cids: List[str], hard_delete: Optional[bool] = None ) -> StreamResponse[DeleteChannelsResponse]: json = build_body_dict(cids=cids, hard_delete=hard_delete) - return self.post( "/api/v2/chat/channels/delete", DeleteChannelsResponse, json=json ) + @telemetry.operation_name("getstream.api.chat.mark_channels_read") def mark_channels_read( self, user_id: Optional[str] = None, @@ -140,9 +142,9 @@ def mark_channels_read( json = build_body_dict( user_id=user_id, read_by_channel=read_by_channel, user=user ) - return self.post("/api/v2/chat/channels/read", MarkReadResponse, json=json) + @telemetry.operation_name("getstream.api.chat.get_or_create_distinct_channel") def get_or_create_distinct_channel( self, type: str, @@ -166,7 +168,6 @@ def get_or_create_distinct_channel( messages=messages, watchers=watchers, ) - return self.post( "/api/v2/chat/channels/{type}/query", ChannelStateResponse, @@ -174,6 +175,7 @@ def get_or_create_distinct_channel( json=json, ) + @telemetry.operation_name("getstream.api.chat.delete_channel") def delete_channel( self, type: str, id: str, hard_delete: Optional[bool] = None ) -> StreamResponse[DeleteChannelResponse]: @@ -182,7 +184,6 @@ def delete_channel( "type": type, "id": id, } - return self.delete( "/api/v2/chat/channels/{type}/{id}", DeleteChannelResponse, @@ -190,6 +191,7 @@ def delete_channel( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.update_channel_partial") def update_channel_partial( self, type: str, @@ -204,7 +206,6 @@ def update_channel_partial( "id": id, } json = build_body_dict(user_id=user_id, unset=unset, set=set, user=user) - return self.patch( "/api/v2/chat/channels/{type}/{id}", UpdateChannelPartialResponse, @@ -212,6 +213,7 @@ def update_channel_partial( json=json, ) + @telemetry.operation_name("getstream.api.chat.update_channel") def update_channel( self, type: str, @@ -253,7 +255,6 @@ def update_channel( message=message, user=user, ) - return self.post( "/api/v2/chat/channels/{type}/{id}", UpdateChannelResponse, @@ -261,6 +262,7 @@ def update_channel( json=json, ) + @telemetry.operation_name("getstream.api.chat.delete_draft") def delete_draft( self, type: str, @@ -273,7 +275,6 @@ def delete_draft( "type": type, "id": id, } - return self.delete( "/api/v2/chat/channels/{type}/{id}/draft", Response, @@ -281,6 +282,7 @@ def delete_draft( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.get_draft") def get_draft( self, type: str, @@ -293,7 +295,6 @@ def get_draft( "type": type, "id": id, } - return self.get( "/api/v2/chat/channels/{type}/{id}/draft", GetDraftResponse, @@ -301,6 +302,7 @@ def get_draft( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.send_event") def send_event( self, type: str, id: str, event: EventRequest ) -> StreamResponse[EventResponse]: @@ -309,7 +311,6 @@ def send_event( "id": id, } json = build_body_dict(event=event) - return self.post( "/api/v2/chat/channels/{type}/{id}/event", EventResponse, @@ -317,6 +318,7 @@ def send_event( json=json, ) + @telemetry.operation_name("getstream.api.chat.delete_file") def delete_file( self, type: str, id: str, url: Optional[str] = None ) -> StreamResponse[Response]: @@ -325,7 +327,6 @@ def delete_file( "type": type, "id": id, } - return self.delete( "/api/v2/chat/channels/{type}/{id}/file", Response, @@ -333,6 +334,7 @@ def delete_file( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.upload_file") def upload_file( self, type: str, @@ -345,7 +347,6 @@ def upload_file( "id": id, } json = build_body_dict(file=file, user=user) - return self.post( "/api/v2/chat/channels/{type}/{id}/file", FileUploadResponse, @@ -353,6 +354,7 @@ def upload_file( json=json, ) + @telemetry.operation_name("getstream.api.chat.hide_channel") def hide_channel( self, type: str, @@ -366,7 +368,6 @@ def hide_channel( "id": id, } json = build_body_dict(clear_history=clear_history, user_id=user_id, user=user) - return self.post( "/api/v2/chat/channels/{type}/{id}/hide", HideChannelResponse, @@ -374,6 +375,7 @@ def hide_channel( json=json, ) + @telemetry.operation_name("getstream.api.chat.delete_image") def delete_image( self, type: str, id: str, url: Optional[str] = None ) -> StreamResponse[Response]: @@ -382,7 +384,6 @@ def delete_image( "type": type, "id": id, } - return self.delete( "/api/v2/chat/channels/{type}/{id}/image", Response, @@ -390,6 +391,7 @@ def delete_image( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.upload_image") def upload_image( self, type: str, @@ -403,7 +405,6 @@ def upload_image( "id": id, } json = build_body_dict(file=file, upload_sizes=upload_sizes, user=user) - return self.post( "/api/v2/chat/channels/{type}/{id}/image", ImageUploadResponse, @@ -411,6 +412,7 @@ def upload_image( json=json, ) + @telemetry.operation_name("getstream.api.chat.update_member_partial") def update_member_partial( self, type: str, @@ -425,7 +427,6 @@ def update_member_partial( "id": id, } json = build_body_dict(unset=unset, set=set) - return self.patch( "/api/v2/chat/channels/{type}/{id}/member", UpdateMemberPartialResponse, @@ -434,6 +435,7 @@ def update_member_partial( json=json, ) + @telemetry.operation_name("getstream.api.chat.send_message") def send_message( self, type: str, @@ -459,7 +461,6 @@ def send_message( skip_push=skip_push, pending_message_metadata=pending_message_metadata, ) - return self.post( "/api/v2/chat/channels/{type}/{id}/message", SendMessageResponse, @@ -467,6 +468,7 @@ def send_message( json=json, ) + @telemetry.operation_name("getstream.api.chat.get_many_messages") def get_many_messages( self, type: str, id: str, ids: List[str] ) -> StreamResponse[GetManyMessagesResponse]: @@ -475,7 +477,6 @@ def get_many_messages( "type": type, "id": id, } - return self.get( "/api/v2/chat/channels/{type}/{id}/messages", GetManyMessagesResponse, @@ -483,6 +484,7 @@ def get_many_messages( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.get_or_create_channel") def get_or_create_channel( self, type: str, @@ -508,7 +510,6 @@ def get_or_create_channel( messages=messages, watchers=watchers, ) - return self.post( "/api/v2/chat/channels/{type}/{id}/query", ChannelStateResponse, @@ -516,6 +517,7 @@ def get_or_create_channel( json=json, ) + @telemetry.operation_name("getstream.api.chat.mark_read") def mark_read( self, type: str, @@ -532,7 +534,6 @@ def mark_read( json = build_body_dict( message_id=message_id, thread_id=thread_id, user_id=user_id, user=user ) - return self.post( "/api/v2/chat/channels/{type}/{id}/read", MarkReadResponse, @@ -540,6 +541,7 @@ def mark_read( json=json, ) + @telemetry.operation_name("getstream.api.chat.show_channel") def show_channel( self, type: str, @@ -552,7 +554,6 @@ def show_channel( "id": id, } json = build_body_dict(user_id=user_id, user=user) - return self.post( "/api/v2/chat/channels/{type}/{id}/show", ShowChannelResponse, @@ -560,6 +561,7 @@ def show_channel( json=json, ) + @telemetry.operation_name("getstream.api.chat.truncate_channel") def truncate_channel( self, type: str, @@ -585,7 +587,6 @@ def truncate_channel( message=message, user=user, ) - return self.post( "/api/v2/chat/channels/{type}/{id}/truncate", TruncateChannelResponse, @@ -593,6 +594,7 @@ def truncate_channel( json=json, ) + @telemetry.operation_name("getstream.api.chat.mark_unread") def mark_unread( self, type: str, @@ -609,7 +611,6 @@ def mark_unread( json = build_body_dict( message_id=message_id, thread_id=thread_id, user_id=user_id, user=user ) - return self.post( "/api/v2/chat/channels/{type}/{id}/unread", Response, @@ -617,9 +618,11 @@ def mark_unread( json=json, ) + @telemetry.operation_name("getstream.api.chat.list_channel_types") def list_channel_types(self) -> StreamResponse[ListChannelTypesResponse]: return self.get("/api/v2/chat/channeltypes", ListChannelTypesResponse) + @telemetry.operation_name("getstream.api.chat.create_channel_type") def create_channel_type( self, automod: str, @@ -683,31 +686,31 @@ def create_channel_type( permissions=permissions, grants=grants, ) - return self.post( "/api/v2/chat/channeltypes", CreateChannelTypeResponse, json=json ) + @telemetry.operation_name("getstream.api.chat.delete_channel_type") def delete_channel_type(self, name: str) -> StreamResponse[Response]: path_params = { "name": name, } - return self.delete( "/api/v2/chat/channeltypes/{name}", Response, path_params=path_params ) + @telemetry.operation_name("getstream.api.chat.get_channel_type") def get_channel_type(self, name: str) -> StreamResponse[GetChannelTypeResponse]: path_params = { "name": name, } - return self.get( "/api/v2/chat/channeltypes/{name}", GetChannelTypeResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.update_channel_type") def update_channel_type( self, name: str, @@ -781,7 +784,6 @@ def update_channel_type( automod_thresholds=automod_thresholds, grants=grants, ) - return self.put( "/api/v2/chat/channeltypes/{name}", UpdateChannelTypeResponse, @@ -789,9 +791,11 @@ def update_channel_type( json=json, ) + @telemetry.operation_name("getstream.api.chat.list_commands") def list_commands(self) -> StreamResponse[ListCommandsResponse]: return self.get("/api/v2/chat/commands", ListCommandsResponse) + @telemetry.operation_name("getstream.api.chat.create_command") def create_command( self, description: str, @@ -800,29 +804,29 @@ def create_command( set: Optional[str] = None, ) -> StreamResponse[CreateCommandResponse]: json = build_body_dict(description=description, name=name, args=args, set=set) - return self.post("/api/v2/chat/commands", CreateCommandResponse, json=json) + @telemetry.operation_name("getstream.api.chat.delete_command") def delete_command(self, name: str) -> StreamResponse[DeleteCommandResponse]: path_params = { "name": name, } - return self.delete( "/api/v2/chat/commands/{name}", DeleteCommandResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.get_command") def get_command(self, name: str) -> StreamResponse[GetCommandResponse]: path_params = { "name": name, } - return self.get( "/api/v2/chat/commands/{name}", GetCommandResponse, path_params=path_params ) + @telemetry.operation_name("getstream.api.chat.update_command") def update_command( self, name: str, @@ -834,7 +838,6 @@ def update_command( "name": name, } json = build_body_dict(description=description, args=args, set=set) - return self.put( "/api/v2/chat/commands/{name}", UpdateCommandResponse, @@ -842,6 +845,7 @@ def update_command( json=json, ) + @telemetry.operation_name("getstream.api.chat.query_drafts") def query_drafts( self, limit: Optional[int] = None, @@ -861,9 +865,9 @@ def query_drafts( filter=filter, user=user, ) - return self.post("/api/v2/chat/drafts/query", QueryDraftsResponse, json=json) + @telemetry.operation_name("getstream.api.chat.export_channels") def export_channels( self, channels: List[ChannelExport], @@ -881,20 +885,20 @@ def export_channels( include_truncated_messages=include_truncated_messages, version=version, ) - return self.post( "/api/v2/chat/export_channels", ExportChannelsResponse, json=json ) + @telemetry.operation_name("getstream.api.chat.query_members") def query_members( self, payload: Optional[QueryMembersPayload] = None ) -> StreamResponse[MembersResponse]: query_params = build_query_param(payload=payload) - return self.get( "/api/v2/chat/members", MembersResponse, query_params=query_params ) + @telemetry.operation_name("getstream.api.chat.query_message_history") def query_message_history( self, filter: Dict[str, object], @@ -906,11 +910,11 @@ def query_message_history( json = build_body_dict( filter=filter, limit=limit, next=next, prev=prev, sort=sort ) - return self.post( "/api/v2/chat/messages/history", QueryMessageHistoryResponse, json=json ) + @telemetry.operation_name("getstream.api.chat.delete_message") def delete_message( self, id: str, @@ -924,7 +928,6 @@ def delete_message( path_params = { "id": id, } - return self.delete( "/api/v2/chat/messages/{id}", DeleteMessageResponse, @@ -932,6 +935,7 @@ def delete_message( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.get_message") def get_message( self, id: str, show_deleted_message: Optional[bool] = None ) -> StreamResponse[GetMessageResponse]: @@ -939,7 +943,6 @@ def get_message( path_params = { "id": id, } - return self.get( "/api/v2/chat/messages/{id}", GetMessageResponse, @@ -947,6 +950,7 @@ def get_message( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.update_message") def update_message( self, id: str, @@ -960,7 +964,6 @@ def update_message( json = build_body_dict( message=message, skip_enrich_url=skip_enrich_url, skip_push=skip_push ) - return self.post( "/api/v2/chat/messages/{id}", UpdateMessageResponse, @@ -968,6 +971,7 @@ def update_message( json=json, ) + @telemetry.operation_name("getstream.api.chat.update_message_partial") def update_message_partial( self, id: str, @@ -987,7 +991,6 @@ def update_message_partial( set=set, user=user, ) - return self.put( "/api/v2/chat/messages/{id}", UpdateMessagePartialResponse, @@ -995,6 +998,7 @@ def update_message_partial( json=json, ) + @telemetry.operation_name("getstream.api.chat.run_message_action") def run_message_action( self, id: str, @@ -1006,7 +1010,6 @@ def run_message_action( "id": id, } json = build_body_dict(form_data=form_data, user_id=user_id, user=user) - return self.post( "/api/v2/chat/messages/{id}/action", MessageResponse, @@ -1014,6 +1017,7 @@ def run_message_action( json=json, ) + @telemetry.operation_name("getstream.api.chat.commit_message") def commit_message( self, id: str, @@ -1022,7 +1026,6 @@ def commit_message( "id": id, } json = build_body_dict() - return self.post( "/api/v2/chat/messages/{id}/commit", MessageResponse, @@ -1030,6 +1033,7 @@ def commit_message( json=json, ) + @telemetry.operation_name("getstream.api.chat.ephemeral_message_update") def ephemeral_message_update( self, id: str, @@ -1049,7 +1053,6 @@ def ephemeral_message_update( set=set, user=user, ) - return self.patch( "/api/v2/chat/messages/{id}/ephemeral", UpdateMessagePartialResponse, @@ -1057,6 +1060,7 @@ def ephemeral_message_update( json=json, ) + @telemetry.operation_name("getstream.api.chat.send_reaction") def send_reaction( self, id: str, @@ -1070,7 +1074,6 @@ def send_reaction( json = build_body_dict( reaction=reaction, enforce_unique=enforce_unique, skip_push=skip_push ) - return self.post( "/api/v2/chat/messages/{id}/reaction", SendReactionResponse, @@ -1078,6 +1081,7 @@ def send_reaction( json=json, ) + @telemetry.operation_name("getstream.api.chat.delete_reaction") def delete_reaction( self, id: str, type: str, user_id: Optional[str] = None ) -> StreamResponse[DeleteReactionResponse]: @@ -1086,7 +1090,6 @@ def delete_reaction( "id": id, "type": type, } - return self.delete( "/api/v2/chat/messages/{id}/reaction/{type}", DeleteReactionResponse, @@ -1094,6 +1097,7 @@ def delete_reaction( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.get_reactions") def get_reactions( self, id: str, limit: Optional[int] = None, offset: Optional[int] = None ) -> StreamResponse[GetReactionsResponse]: @@ -1101,7 +1105,6 @@ def get_reactions( path_params = { "id": id, } - return self.get( "/api/v2/chat/messages/{id}/reactions", GetReactionsResponse, @@ -1109,6 +1112,7 @@ def get_reactions( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.query_reactions") def query_reactions( self, id: str, @@ -1132,7 +1136,6 @@ def query_reactions( filter=filter, user=user, ) - return self.post( "/api/v2/chat/messages/{id}/reactions", QueryReactionsResponse, @@ -1140,6 +1143,7 @@ def query_reactions( json=json, ) + @telemetry.operation_name("getstream.api.chat.translate_message") def translate_message( self, id: str, language: str ) -> StreamResponse[MessageResponse]: @@ -1147,7 +1151,6 @@ def translate_message( "id": id, } json = build_body_dict(language=language) - return self.post( "/api/v2/chat/messages/{id}/translate", MessageResponse, @@ -1155,6 +1158,7 @@ def translate_message( json=json, ) + @telemetry.operation_name("getstream.api.chat.undelete_message") def undelete_message( self, id: str, @@ -1168,7 +1172,6 @@ def undelete_message( json = build_body_dict( message=message, skip_enrich_url=skip_enrich_url, skip_push=skip_push ) - return self.post( "/api/v2/chat/messages/{id}/undelete", UpdateMessageResponse, @@ -1176,6 +1179,7 @@ def undelete_message( json=json, ) + @telemetry.operation_name("getstream.api.chat.cast_poll_vote") def cast_poll_vote( self, message_id: str, @@ -1189,7 +1193,6 @@ def cast_poll_vote( "poll_id": poll_id, } json = build_body_dict(user_id=user_id, user=user, vote=vote) - return self.post( "/api/v2/chat/messages/{message_id}/polls/{poll_id}/vote", PollVoteResponse, @@ -1197,6 +1200,7 @@ def cast_poll_vote( json=json, ) + @telemetry.operation_name("getstream.api.chat.delete_poll_vote") def delete_poll_vote( self, message_id: str, poll_id: str, vote_id: str, user_id: Optional[str] = None ) -> StreamResponse[PollVoteResponse]: @@ -1206,7 +1210,6 @@ def delete_poll_vote( "poll_id": poll_id, "vote_id": vote_id, } - return self.delete( "/api/v2/chat/messages/{message_id}/polls/{poll_id}/vote/{vote_id}", PollVoteResponse, @@ -1214,6 +1217,7 @@ def delete_poll_vote( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.delete_reminder") def delete_reminder( self, message_id: str, user_id: Optional[str] = None ) -> StreamResponse[DeleteReminderResponse]: @@ -1221,7 +1225,6 @@ def delete_reminder( path_params = { "message_id": message_id, } - return self.delete( "/api/v2/chat/messages/{message_id}/reminders", DeleteReminderResponse, @@ -1229,6 +1232,7 @@ def delete_reminder( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.update_reminder") def update_reminder( self, message_id: str, @@ -1240,7 +1244,6 @@ def update_reminder( "message_id": message_id, } json = build_body_dict(remind_at=remind_at, user_id=user_id, user=user) - return self.patch( "/api/v2/chat/messages/{message_id}/reminders", UpdateReminderResponse, @@ -1248,6 +1251,7 @@ def update_reminder( json=json, ) + @telemetry.operation_name("getstream.api.chat.create_reminder") def create_reminder( self, message_id: str, @@ -1259,7 +1263,6 @@ def create_reminder( "message_id": message_id, } json = build_body_dict(remind_at=remind_at, user_id=user_id, user=user) - return self.post( "/api/v2/chat/messages/{message_id}/reminders", ReminderResponseData, @@ -1267,6 +1270,7 @@ def create_reminder( json=json, ) + @telemetry.operation_name("getstream.api.chat.get_replies") def get_replies( self, parent_id: str, @@ -1302,7 +1306,6 @@ def get_replies( path_params = { "parent_id": parent_id, } - return self.get( "/api/v2/chat/messages/{parent_id}/replies", GetRepliesResponse, @@ -1310,17 +1313,18 @@ def get_replies( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.query_message_flags") def query_message_flags( self, payload: Optional[QueryMessageFlagsPayload] = None ) -> StreamResponse[QueryMessageFlagsResponse]: query_params = build_query_param(payload=payload) - return self.get( "/api/v2/chat/moderation/flags/message", QueryMessageFlagsResponse, query_params=query_params, ) + @telemetry.operation_name("getstream.api.chat.mute_channel") def mute_channel( self, expiration: Optional[int] = None, @@ -1331,11 +1335,11 @@ def mute_channel( json = build_body_dict( expiration=expiration, user_id=user_id, channel_cids=channel_cids, user=user ) - return self.post( "/api/v2/chat/moderation/mute/channel", MuteChannelResponse, json=json ) + @telemetry.operation_name("getstream.api.chat.unmute_channel") def unmute_channel( self, expiration: Optional[int] = None, @@ -1346,22 +1350,22 @@ def unmute_channel( json = build_body_dict( expiration=expiration, user_id=user_id, channel_cids=channel_cids, user=user ) - return self.post( "/api/v2/chat/moderation/unmute/channel", UnmuteResponse, json=json ) + @telemetry.operation_name("getstream.api.chat.query_banned_users") def query_banned_users( self, payload: Optional[QueryBannedUsersPayload] = None ) -> StreamResponse[QueryBannedUsersResponse]: query_params = build_query_param(payload=payload) - return self.get( "/api/v2/chat/query_banned_users", QueryBannedUsersResponse, query_params=query_params, ) + @telemetry.operation_name("getstream.api.chat.query_reminders") def query_reminders( self, limit: Optional[int] = None, @@ -1381,20 +1385,20 @@ def query_reminders( filter=filter, user=user, ) - return self.post( "/api/v2/chat/reminders/query", QueryRemindersResponse, json=json ) + @telemetry.operation_name("getstream.api.chat.search") def search( self, payload: Optional[SearchPayload] = None ) -> StreamResponse[SearchResponse]: query_params = build_query_param(payload=payload) - return self.get( "/api/v2/chat/search", SearchResponse, query_params=query_params ) + @telemetry.operation_name("getstream.api.chat.query_segments") def query_segments( self, filter: Dict[str, object], @@ -1406,29 +1410,29 @@ def query_segments( json = build_body_dict( filter=filter, limit=limit, next=next, prev=prev, sort=sort ) - return self.post( "/api/v2/chat/segments/query", QuerySegmentsResponse, json=json ) + @telemetry.operation_name("getstream.api.chat.delete_segment") def delete_segment(self, id: str) -> StreamResponse[Response]: path_params = { "id": id, } - return self.delete( "/api/v2/chat/segments/{id}", Response, path_params=path_params ) + @telemetry.operation_name("getstream.api.chat.get_segment") def get_segment(self, id: str) -> StreamResponse[GetSegmentResponse]: path_params = { "id": id, } - return self.get( "/api/v2/chat/segments/{id}", GetSegmentResponse, path_params=path_params ) + @telemetry.operation_name("getstream.api.chat.delete_segment_targets") def delete_segment_targets( self, id: str, target_ids: List[str] ) -> StreamResponse[Response]: @@ -1436,7 +1440,6 @@ def delete_segment_targets( "id": id, } json = build_body_dict(target_ids=target_ids) - return self.post( "/api/v2/chat/segments/{id}/deletetargets", Response, @@ -1444,6 +1447,7 @@ def delete_segment_targets( json=json, ) + @telemetry.operation_name("getstream.api.chat.segment_target_exists") def segment_target_exists( self, id: str, target_id: str ) -> StreamResponse[Response]: @@ -1451,13 +1455,13 @@ def segment_target_exists( "id": id, "target_id": target_id, } - return self.get( "/api/v2/chat/segments/{id}/target/{target_id}", Response, path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.query_segment_targets") def query_segment_targets( self, id: str, @@ -1473,7 +1477,6 @@ def query_segment_targets( json = build_body_dict( limit=limit, next=next, prev=prev, sort=sort, filter=filter ) - return self.post( "/api/v2/chat/segments/{id}/targets/query", QuerySegmentTargetsResponse, @@ -1481,6 +1484,7 @@ def query_segment_targets( json=json, ) + @telemetry.operation_name("getstream.api.chat.query_threads") def query_threads( self, limit: Optional[int] = None, @@ -1506,9 +1510,9 @@ def query_threads( filter=filter, user=user, ) - return self.post("/api/v2/chat/threads", QueryThreadsResponse, json=json) + @telemetry.operation_name("getstream.api.chat.get_thread") def get_thread( self, message_id: str, @@ -1524,7 +1528,6 @@ def get_thread( path_params = { "message_id": message_id, } - return self.get( "/api/v2/chat/threads/{message_id}", GetThreadResponse, @@ -1532,6 +1535,7 @@ def get_thread( path_params=path_params, ) + @telemetry.operation_name("getstream.api.chat.update_thread_partial") def update_thread_partial( self, message_id: str, @@ -1544,7 +1548,6 @@ def update_thread_partial( "message_id": message_id, } json = build_body_dict(user_id=user_id, unset=unset, set=set, user=user) - return self.patch( "/api/v2/chat/threads/{message_id}", UpdateThreadPartialResponse, @@ -1552,18 +1555,20 @@ def update_thread_partial( json=json, ) + @telemetry.operation_name("getstream.api.chat.unread_counts") def unread_counts(self) -> StreamResponse[WrappedUnreadCountsResponse]: return self.get("/api/v2/chat/unread", WrappedUnreadCountsResponse) + @telemetry.operation_name("getstream.api.chat.unread_counts_batch") def unread_counts_batch( self, user_ids: List[str] ) -> StreamResponse[UnreadCountsBatchResponse]: json = build_body_dict(user_ids=user_ids) - return self.post( "/api/v2/chat/unread_batch", UnreadCountsBatchResponse, json=json ) + @telemetry.operation_name("getstream.api.chat.send_user_custom_event") def send_user_custom_event( self, user_id: str, event: UserCustomEventRequest ) -> StreamResponse[Response]: @@ -1571,7 +1576,6 @@ def send_user_custom_event( "user_id": user_id, } json = build_body_dict(event=event) - return self.post( "/api/v2/chat/users/{user_id}/event", Response, diff --git a/getstream/common/async_rest_client.py b/getstream/common/async_rest_client.py index 99cf710a..7080efa9 100644 --- a/getstream/common/async_rest_client.py +++ b/getstream/common/async_rest_client.py @@ -1,5 +1,6 @@ # Code generated by GetStream internal OpenAPI code generator. DO NOT EDIT. from getstream.base import AsyncBaseClient +from getstream.common import telemetry from getstream.models import * from getstream.stream_response import StreamResponse from getstream.utils import build_query_param, build_body_dict @@ -21,9 +22,11 @@ def __init__(self, api_key: str, base_url: str, timeout: float, token: str): token=token, ) + @telemetry.operation_name("getstream.api.common.get_app") async def get_app(self) -> StreamResponse[GetApplicationResponse]: return await self.get("/api/v2/app", GetApplicationResponse) + @telemetry.operation_name("getstream.api.common.update_app") async def update_app( self, async_url_enrich_enabled: Optional[bool] = None, @@ -125,18 +128,18 @@ async def update_app( push_config=push_config, xiaomi_config=xiaomi_config, ) - return await self.patch("/api/v2/app", Response, json=json) + @telemetry.operation_name("getstream.api.common.list_block_lists") async def list_block_lists( self, team: Optional[str] = None ) -> StreamResponse[ListBlockListResponse]: query_params = build_query_param(team=team) - return await self.get( "/api/v2/blocklists", ListBlockListResponse, query_params=query_params ) + @telemetry.operation_name("getstream.api.common.create_block_list") async def create_block_list( self, name: str, @@ -145,9 +148,9 @@ async def create_block_list( type: Optional[str] = None, ) -> StreamResponse[CreateBlockListResponse]: json = build_body_dict(name=name, words=words, team=team, type=type) - return await self.post("/api/v2/blocklists", CreateBlockListResponse, json=json) + @telemetry.operation_name("getstream.api.common.delete_block_list") async def delete_block_list( self, name: str, team: Optional[str] = None ) -> StreamResponse[Response]: @@ -155,7 +158,6 @@ async def delete_block_list( path_params = { "name": name, } - return await self.delete( "/api/v2/blocklists/{name}", Response, @@ -163,6 +165,7 @@ async def delete_block_list( path_params=path_params, ) + @telemetry.operation_name("getstream.api.common.get_block_list") async def get_block_list( self, name: str, team: Optional[str] = None ) -> StreamResponse[GetBlockListResponse]: @@ -170,7 +173,6 @@ async def get_block_list( path_params = { "name": name, } - return await self.get( "/api/v2/blocklists/{name}", GetBlockListResponse, @@ -178,6 +180,7 @@ async def get_block_list( path_params=path_params, ) + @telemetry.operation_name("getstream.api.common.update_block_list") async def update_block_list( self, name: str, team: Optional[str] = None, words: Optional[List[str]] = None ) -> StreamResponse[UpdateBlockListResponse]: @@ -185,7 +188,6 @@ async def update_block_list( "name": name, } json = build_body_dict(team=team, words=words) - return await self.put( "/api/v2/blocklists/{name}", UpdateBlockListResponse, @@ -193,6 +195,7 @@ async def update_block_list( json=json, ) + @telemetry.operation_name("getstream.api.common.check_push") async def check_push( self, apn_template: Optional[str] = None, @@ -218,9 +221,9 @@ async def check_push( user_id=user_id, user=user, ) - return await self.post("/api/v2/check_push", CheckPushResponse, json=json) + @telemetry.operation_name("getstream.api.common.check_sns") async def check_sns( self, sns_key: Optional[str] = None, @@ -230,9 +233,9 @@ async def check_sns( json = build_body_dict( sns_key=sns_key, sns_secret=sns_secret, sns_topic_arn=sns_topic_arn ) - return await self.post("/api/v2/check_sns", CheckSNSResponse, json=json) + @telemetry.operation_name("getstream.api.common.check_sqs") async def check_sqs( self, sqs_key: Optional[str] = None, @@ -240,25 +243,25 @@ async def check_sqs( sqs_url: Optional[str] = None, ) -> StreamResponse[CheckSQSResponse]: json = build_body_dict(sqs_key=sqs_key, sqs_secret=sqs_secret, sqs_url=sqs_url) - return await self.post("/api/v2/check_sqs", CheckSQSResponse, json=json) + @telemetry.operation_name("getstream.api.common.delete_device") async def delete_device( self, id: str, user_id: Optional[str] = None ) -> StreamResponse[Response]: query_params = build_query_param(id=id, user_id=user_id) - return await self.delete("/api/v2/devices", Response, query_params=query_params) + @telemetry.operation_name("getstream.api.common.list_devices") async def list_devices( self, user_id: Optional[str] = None ) -> StreamResponse[ListDevicesResponse]: query_params = build_query_param(user_id=user_id) - return await self.get( "/api/v2/devices", ListDevicesResponse, query_params=query_params ) + @telemetry.operation_name("getstream.api.common.create_device") async def create_device( self, id: str, @@ -276,21 +279,22 @@ async def create_device( voip_token=voip_token, user=user, ) - return await self.post("/api/v2/devices", Response, json=json) + @telemetry.operation_name("getstream.api.common.export_users") async def export_users( self, user_ids: List[str] ) -> StreamResponse[ExportUsersResponse]: json = build_body_dict(user_ids=user_ids) - return await self.post("/api/v2/export/users", ExportUsersResponse, json=json) + @telemetry.operation_name("getstream.api.common.list_external_storage") async def list_external_storage( self, ) -> StreamResponse[ListExternalStorageResponse]: return await self.get("/api/v2/external_storage", ListExternalStorageResponse) + @telemetry.operation_name("getstream.api.common.create_external_storage") async def create_external_storage( self, bucket: str, @@ -310,24 +314,24 @@ async def create_external_storage( aws_s3=aws_s3, azure_blob=azure_blob, ) - return await self.post( "/api/v2/external_storage", CreateExternalStorageResponse, json=json ) + @telemetry.operation_name("getstream.api.common.delete_external_storage") async def delete_external_storage( self, name: str ) -> StreamResponse[DeleteExternalStorageResponse]: path_params = { "name": name, } - return await self.delete( "/api/v2/external_storage/{name}", DeleteExternalStorageResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.common.update_external_storage") async def update_external_storage( self, name: str, @@ -349,7 +353,6 @@ async def update_external_storage( aws_s3=aws_s3, azure_blob=azure_blob, ) - return await self.put( "/api/v2/external_storage/{name}", UpdateExternalStorageResponse, @@ -357,75 +360,78 @@ async def update_external_storage( json=json, ) + @telemetry.operation_name("getstream.api.common.check_external_storage") async def check_external_storage( self, name: str ) -> StreamResponse[CheckExternalStorageResponse]: path_params = { "name": name, } - return await self.get( "/api/v2/external_storage/{name}/check", CheckExternalStorageResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.common.create_guest") async def create_guest( self, user: UserRequest ) -> StreamResponse[CreateGuestResponse]: json = build_body_dict(user=user) - return await self.post("/api/v2/guest", CreateGuestResponse, json=json) + @telemetry.operation_name("getstream.api.common.create_import_url") async def create_import_url( self, filename: Optional[str] = None ) -> StreamResponse[CreateImportURLResponse]: json = build_body_dict(filename=filename) - return await self.post( "/api/v2/import_urls", CreateImportURLResponse, json=json ) + @telemetry.operation_name("getstream.api.common.list_imports") async def list_imports(self) -> StreamResponse[ListImportsResponse]: return await self.get("/api/v2/imports", ListImportsResponse) + @telemetry.operation_name("getstream.api.common.create_import") async def create_import( self, mode: str, path: str ) -> StreamResponse[CreateImportResponse]: json = build_body_dict(mode=mode, path=path) - return await self.post("/api/v2/imports", CreateImportResponse, json=json) + @telemetry.operation_name("getstream.api.common.get_import") async def get_import(self, id: str) -> StreamResponse[GetImportResponse]: path_params = { "id": id, } - return await self.get( "/api/v2/imports/{id}", GetImportResponse, path_params=path_params ) + @telemetry.operation_name("getstream.api.common.get_og") async def get_og(self, url: str) -> StreamResponse[GetOGResponse]: query_params = build_query_param(url=url) - return await self.get("/api/v2/og", GetOGResponse, query_params=query_params) + @telemetry.operation_name("getstream.api.common.list_permissions") async def list_permissions(self) -> StreamResponse[ListPermissionsResponse]: return await self.get("/api/v2/permissions", ListPermissionsResponse) + @telemetry.operation_name("getstream.api.common.get_permission") async def get_permission( self, id: str ) -> StreamResponse[GetCustomPermissionResponse]: path_params = { "id": id, } - return await self.get( "/api/v2/permissions/{id}", GetCustomPermissionResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.common.create_poll") async def create_poll( self, name: str, @@ -457,9 +463,9 @@ async def create_poll( custom=custom, user=user, ) - return await self.post("/api/v2/polls", PollResponse, json=json) + @telemetry.operation_name("getstream.api.common.update_poll") async def update_poll( self, id: str, @@ -491,9 +497,9 @@ async def update_poll( custom=custom, user=user, ) - return await self.put("/api/v2/polls", PollResponse, json=json) + @telemetry.operation_name("getstream.api.common.query_polls") async def query_polls( self, user_id: Optional[str] = None, @@ -507,7 +513,6 @@ async def query_polls( json = build_body_dict( limit=limit, next=next, prev=prev, sort=sort, filter=filter ) - return await self.post( "/api/v2/polls/query", QueryPollsResponse, @@ -515,6 +520,7 @@ async def query_polls( json=json, ) + @telemetry.operation_name("getstream.api.common.delete_poll") async def delete_poll( self, poll_id: str, user_id: Optional[str] = None ) -> StreamResponse[Response]: @@ -522,7 +528,6 @@ async def delete_poll( path_params = { "poll_id": poll_id, } - return await self.delete( "/api/v2/polls/{poll_id}", Response, @@ -530,6 +535,7 @@ async def delete_poll( path_params=path_params, ) + @telemetry.operation_name("getstream.api.common.get_poll") async def get_poll( self, poll_id: str, user_id: Optional[str] = None ) -> StreamResponse[PollResponse]: @@ -537,7 +543,6 @@ async def get_poll( path_params = { "poll_id": poll_id, } - return await self.get( "/api/v2/polls/{poll_id}", PollResponse, @@ -545,6 +550,7 @@ async def get_poll( path_params=path_params, ) + @telemetry.operation_name("getstream.api.common.update_poll_partial") async def update_poll_partial( self, poll_id: str, @@ -557,11 +563,11 @@ async def update_poll_partial( "poll_id": poll_id, } json = build_body_dict(user_id=user_id, unset=unset, set=set, user=user) - return await self.patch( "/api/v2/polls/{poll_id}", PollResponse, path_params=path_params, json=json ) + @telemetry.operation_name("getstream.api.common.create_poll_option") async def create_poll_option( self, poll_id: str, @@ -574,7 +580,6 @@ async def create_poll_option( "poll_id": poll_id, } json = build_body_dict(text=text, user_id=user_id, custom=custom, user=user) - return await self.post( "/api/v2/polls/{poll_id}/options", PollOptionResponse, @@ -582,6 +587,7 @@ async def create_poll_option( json=json, ) + @telemetry.operation_name("getstream.api.common.update_poll_option") async def update_poll_option( self, poll_id: str, @@ -597,7 +603,6 @@ async def update_poll_option( json = build_body_dict( id=id, text=text, user_id=user_id, custom=custom, user=user ) - return await self.put( "/api/v2/polls/{poll_id}/options", PollOptionResponse, @@ -605,6 +610,7 @@ async def update_poll_option( json=json, ) + @telemetry.operation_name("getstream.api.common.delete_poll_option") async def delete_poll_option( self, poll_id: str, option_id: str, user_id: Optional[str] = None ) -> StreamResponse[Response]: @@ -613,7 +619,6 @@ async def delete_poll_option( "poll_id": poll_id, "option_id": option_id, } - return await self.delete( "/api/v2/polls/{poll_id}/options/{option_id}", Response, @@ -621,6 +626,7 @@ async def delete_poll_option( path_params=path_params, ) + @telemetry.operation_name("getstream.api.common.get_poll_option") async def get_poll_option( self, poll_id: str, option_id: str, user_id: Optional[str] = None ) -> StreamResponse[PollOptionResponse]: @@ -629,7 +635,6 @@ async def get_poll_option( "poll_id": poll_id, "option_id": option_id, } - return await self.get( "/api/v2/polls/{poll_id}/options/{option_id}", PollOptionResponse, @@ -637,6 +642,7 @@ async def get_poll_option( path_params=path_params, ) + @telemetry.operation_name("getstream.api.common.query_poll_votes") async def query_poll_votes( self, poll_id: str, @@ -654,7 +660,6 @@ async def query_poll_votes( json = build_body_dict( limit=limit, next=next, prev=prev, sort=sort, filter=filter ) - return await self.post( "/api/v2/polls/{poll_id}/votes", PollVotesResponse, @@ -663,27 +668,31 @@ async def query_poll_votes( json=json, ) + @telemetry.operation_name( + "getstream.api.common.update_push_notification_preferences" + ) async def update_push_notification_preferences( self, preferences: List[PushPreferenceInput] ) -> StreamResponse[UpsertPushPreferencesResponse]: json = build_body_dict(preferences=preferences) - return await self.post( "/api/v2/push_preferences", UpsertPushPreferencesResponse, json=json ) + @telemetry.operation_name("getstream.api.common.list_push_providers") async def list_push_providers(self) -> StreamResponse[ListPushProvidersResponse]: return await self.get("/api/v2/push_providers", ListPushProvidersResponse) + @telemetry.operation_name("getstream.api.common.upsert_push_provider") async def upsert_push_provider( self, push_provider: Optional[PushProvider] = None ) -> StreamResponse[UpsertPushProviderResponse]: json = build_body_dict(push_provider=push_provider) - return await self.post( "/api/v2/push_providers", UpsertPushProviderResponse, json=json ) + @telemetry.operation_name("getstream.api.common.delete_push_provider") async def delete_push_provider( self, type: str, name: str ) -> StreamResponse[Response]: @@ -691,24 +700,24 @@ async def delete_push_provider( "type": type, "name": name, } - return await self.delete( "/api/v2/push_providers/{type}/{name}", Response, path_params=path_params ) + @telemetry.operation_name("getstream.api.common.get_push_templates") async def get_push_templates( self, push_provider_type: str, push_provider_name: Optional[str] = None ) -> StreamResponse[GetPushTemplatesResponse]: query_params = build_query_param( push_provider_type=push_provider_type, push_provider_name=push_provider_name ) - return await self.get( "/api/v2/push_templates", GetPushTemplatesResponse, query_params=query_params, ) + @telemetry.operation_name("getstream.api.common.upsert_push_template") async def upsert_push_template( self, event_type: str, @@ -724,11 +733,11 @@ async def upsert_push_template( push_provider_name=push_provider_name, template=template, ) - return await self.post( "/api/v2/push_templates", UpsertPushTemplateResponse, json=json ) + @telemetry.operation_name("getstream.api.common.get_rate_limits") async def get_rate_limits( self, server_side: Optional[bool] = None, @@ -744,58 +753,59 @@ async def get_rate_limits( web=web, endpoints=endpoints, ) - return await self.get( "/api/v2/rate_limits", GetRateLimitsResponse, query_params=query_params ) + @telemetry.operation_name("getstream.api.common.list_roles") async def list_roles(self) -> StreamResponse[ListRolesResponse]: return await self.get("/api/v2/roles", ListRolesResponse) + @telemetry.operation_name("getstream.api.common.create_role") async def create_role(self, name: str) -> StreamResponse[CreateRoleResponse]: json = build_body_dict(name=name) - return await self.post("/api/v2/roles", CreateRoleResponse, json=json) + @telemetry.operation_name("getstream.api.common.delete_role") async def delete_role(self, name: str) -> StreamResponse[Response]: path_params = { "name": name, } - return await self.delete( "/api/v2/roles/{name}", Response, path_params=path_params ) + @telemetry.operation_name("getstream.api.common.get_task") async def get_task(self, id: str) -> StreamResponse[GetTaskResponse]: path_params = { "id": id, } - return await self.get( "/api/v2/tasks/{id}", GetTaskResponse, path_params=path_params ) + @telemetry.operation_name("getstream.api.common.delete_file") async def delete_file(self, url: Optional[str] = None) -> StreamResponse[Response]: query_params = build_query_param(url=url) - return await self.delete( "/api/v2/uploads/file", Response, query_params=query_params ) + @telemetry.operation_name("getstream.api.common.upload_file") async def upload_file( self, file: Optional[str] = None, user: Optional[OnlyUserID] = None ) -> StreamResponse[FileUploadResponse]: json = build_body_dict(file=file, user=user) - return await self.post("/api/v2/uploads/file", FileUploadResponse, json=json) + @telemetry.operation_name("getstream.api.common.delete_image") async def delete_image(self, url: Optional[str] = None) -> StreamResponse[Response]: query_params = build_query_param(url=url) - return await self.delete( "/api/v2/uploads/image", Response, query_params=query_params ) + @telemetry.operation_name("getstream.api.common.upload_image") async def upload_image( self, file: Optional[str] = None, @@ -803,41 +813,41 @@ async def upload_image( user: Optional[OnlyUserID] = None, ) -> StreamResponse[ImageUploadResponse]: json = build_body_dict(file=file, upload_sizes=upload_sizes, user=user) - return await self.post("/api/v2/uploads/image", ImageUploadResponse, json=json) + @telemetry.operation_name("getstream.api.common.query_users") async def query_users( self, payload: Optional[QueryUsersPayload] = None ) -> StreamResponse[QueryUsersResponse]: query_params = build_query_param(payload=payload) - return await self.get( "/api/v2/users", QueryUsersResponse, query_params=query_params ) + @telemetry.operation_name("getstream.api.common.update_users_partial") async def update_users_partial( self, users: List[UpdateUserPartialRequest] ) -> StreamResponse[UpdateUsersResponse]: json = build_body_dict(users=users) - return await self.patch("/api/v2/users", UpdateUsersResponse, json=json) + @telemetry.operation_name("getstream.api.common.update_users") async def update_users( self, users: Dict[str, UserRequest] ) -> StreamResponse[UpdateUsersResponse]: json = build_body_dict(users=users) - return await self.post("/api/v2/users", UpdateUsersResponse, json=json) + @telemetry.operation_name("getstream.api.common.get_blocked_users") async def get_blocked_users( self, user_id: Optional[str] = None ) -> StreamResponse[GetBlockedUsersResponse]: query_params = build_query_param(user_id=user_id) - return await self.get( "/api/v2/users/block", GetBlockedUsersResponse, query_params=query_params ) + @telemetry.operation_name("getstream.api.common.block_users") async def block_users( self, blocked_user_id: str, @@ -847,9 +857,9 @@ async def block_users( json = build_body_dict( blocked_user_id=blocked_user_id, user_id=user_id, user=user ) - return await self.post("/api/v2/users/block", BlockUsersResponse, json=json) + @telemetry.operation_name("getstream.api.common.deactivate_users") async def deactivate_users( self, user_ids: List[str], @@ -863,11 +873,11 @@ async def deactivate_users( mark_channels_deleted=mark_channels_deleted, mark_messages_deleted=mark_messages_deleted, ) - return await self.post( "/api/v2/users/deactivate", DeactivateUsersResponse, json=json ) + @telemetry.operation_name("getstream.api.common.delete_users") async def delete_users( self, user_ids: List[str], @@ -889,20 +899,20 @@ async def delete_users( new_channel_owner_id=new_channel_owner_id, user=user, ) - return await self.post("/api/v2/users/delete", DeleteUsersResponse, json=json) + @telemetry.operation_name("getstream.api.common.get_user_live_locations") async def get_user_live_locations( self, user_id: Optional[str] = None ) -> StreamResponse[SharedLocationsResponse]: query_params = build_query_param(user_id=user_id) - return await self.get( "/api/v2/users/live_locations", SharedLocationsResponse, query_params=query_params, ) + @telemetry.operation_name("getstream.api.common.update_live_location") async def update_live_location( self, message_id: str, @@ -915,7 +925,6 @@ async def update_live_location( json = build_body_dict( message_id=message_id, end_at=end_at, latitude=latitude, longitude=longitude ) - return await self.put( "/api/v2/users/live_locations", SharedLocationResponse, @@ -923,6 +932,7 @@ async def update_live_location( json=json, ) + @telemetry.operation_name("getstream.api.common.reactivate_users") async def reactivate_users( self, user_ids: List[str], @@ -936,16 +946,16 @@ async def reactivate_users( restore_channels=restore_channels, restore_messages=restore_messages, ) - return await self.post( "/api/v2/users/reactivate", ReactivateUsersResponse, json=json ) + @telemetry.operation_name("getstream.api.common.restore_users") async def restore_users(self, user_ids: List[str]) -> StreamResponse[Response]: json = build_body_dict(user_ids=user_ids) - return await self.post("/api/v2/users/restore", Response, json=json) + @telemetry.operation_name("getstream.api.common.unblock_users") async def unblock_users( self, blocked_user_id: str, @@ -955,9 +965,9 @@ async def unblock_users( json = build_body_dict( blocked_user_id=blocked_user_id, user_id=user_id, user=user ) - return await self.post("/api/v2/users/unblock", UnblockUsersResponse, json=json) + @telemetry.operation_name("getstream.api.common.deactivate_user") async def deactivate_user( self, user_id: str, @@ -970,7 +980,6 @@ async def deactivate_user( json = build_body_dict( created_by_id=created_by_id, mark_messages_deleted=mark_messages_deleted ) - return await self.post( "/api/v2/users/{user_id}/deactivate", DeactivateUserResponse, @@ -978,17 +987,18 @@ async def deactivate_user( json=json, ) + @telemetry.operation_name("getstream.api.common.export_user") async def export_user(self, user_id: str) -> StreamResponse[ExportUserResponse]: path_params = { "user_id": user_id, } - return await self.get( "/api/v2/users/{user_id}/export", ExportUserResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.common.reactivate_user") async def reactivate_user( self, user_id: str, @@ -1002,7 +1012,6 @@ async def reactivate_user( json = build_body_dict( created_by_id=created_by_id, name=name, restore_messages=restore_messages ) - return await self.post( "/api/v2/users/{user_id}/reactivate", ReactivateUserResponse, diff --git a/getstream/common/rest_client.py b/getstream/common/rest_client.py index d3cca768..3bb5ec08 100644 --- a/getstream/common/rest_client.py +++ b/getstream/common/rest_client.py @@ -1,5 +1,6 @@ # Code generated by GetStream internal OpenAPI code generator. DO NOT EDIT. from getstream.base import BaseClient +from getstream.common import telemetry from getstream.models import * from getstream.stream_response import StreamResponse from getstream.utils import build_query_param, build_body_dict @@ -21,9 +22,11 @@ def __init__(self, api_key: str, base_url: str, timeout: float, token: str): token=token, ) + @telemetry.operation_name("getstream.api.common.get_app") def get_app(self) -> StreamResponse[GetApplicationResponse]: return self.get("/api/v2/app", GetApplicationResponse) + @telemetry.operation_name("getstream.api.common.update_app") def update_app( self, async_url_enrich_enabled: Optional[bool] = None, @@ -125,18 +128,18 @@ def update_app( push_config=push_config, xiaomi_config=xiaomi_config, ) - return self.patch("/api/v2/app", Response, json=json) + @telemetry.operation_name("getstream.api.common.list_block_lists") def list_block_lists( self, team: Optional[str] = None ) -> StreamResponse[ListBlockListResponse]: query_params = build_query_param(team=team) - return self.get( "/api/v2/blocklists", ListBlockListResponse, query_params=query_params ) + @telemetry.operation_name("getstream.api.common.create_block_list") def create_block_list( self, name: str, @@ -145,9 +148,9 @@ def create_block_list( type: Optional[str] = None, ) -> StreamResponse[CreateBlockListResponse]: json = build_body_dict(name=name, words=words, team=team, type=type) - return self.post("/api/v2/blocklists", CreateBlockListResponse, json=json) + @telemetry.operation_name("getstream.api.common.delete_block_list") def delete_block_list( self, name: str, team: Optional[str] = None ) -> StreamResponse[Response]: @@ -155,7 +158,6 @@ def delete_block_list( path_params = { "name": name, } - return self.delete( "/api/v2/blocklists/{name}", Response, @@ -163,6 +165,7 @@ def delete_block_list( path_params=path_params, ) + @telemetry.operation_name("getstream.api.common.get_block_list") def get_block_list( self, name: str, team: Optional[str] = None ) -> StreamResponse[GetBlockListResponse]: @@ -170,7 +173,6 @@ def get_block_list( path_params = { "name": name, } - return self.get( "/api/v2/blocklists/{name}", GetBlockListResponse, @@ -178,6 +180,7 @@ def get_block_list( path_params=path_params, ) + @telemetry.operation_name("getstream.api.common.update_block_list") def update_block_list( self, name: str, team: Optional[str] = None, words: Optional[List[str]] = None ) -> StreamResponse[UpdateBlockListResponse]: @@ -185,7 +188,6 @@ def update_block_list( "name": name, } json = build_body_dict(team=team, words=words) - return self.put( "/api/v2/blocklists/{name}", UpdateBlockListResponse, @@ -193,6 +195,7 @@ def update_block_list( json=json, ) + @telemetry.operation_name("getstream.api.common.check_push") def check_push( self, apn_template: Optional[str] = None, @@ -218,9 +221,9 @@ def check_push( user_id=user_id, user=user, ) - return self.post("/api/v2/check_push", CheckPushResponse, json=json) + @telemetry.operation_name("getstream.api.common.check_sns") def check_sns( self, sns_key: Optional[str] = None, @@ -230,9 +233,9 @@ def check_sns( json = build_body_dict( sns_key=sns_key, sns_secret=sns_secret, sns_topic_arn=sns_topic_arn ) - return self.post("/api/v2/check_sns", CheckSNSResponse, json=json) + @telemetry.operation_name("getstream.api.common.check_sqs") def check_sqs( self, sqs_key: Optional[str] = None, @@ -240,25 +243,25 @@ def check_sqs( sqs_url: Optional[str] = None, ) -> StreamResponse[CheckSQSResponse]: json = build_body_dict(sqs_key=sqs_key, sqs_secret=sqs_secret, sqs_url=sqs_url) - return self.post("/api/v2/check_sqs", CheckSQSResponse, json=json) + @telemetry.operation_name("getstream.api.common.delete_device") def delete_device( self, id: str, user_id: Optional[str] = None ) -> StreamResponse[Response]: query_params = build_query_param(id=id, user_id=user_id) - return self.delete("/api/v2/devices", Response, query_params=query_params) + @telemetry.operation_name("getstream.api.common.list_devices") def list_devices( self, user_id: Optional[str] = None ) -> StreamResponse[ListDevicesResponse]: query_params = build_query_param(user_id=user_id) - return self.get( "/api/v2/devices", ListDevicesResponse, query_params=query_params ) + @telemetry.operation_name("getstream.api.common.create_device") def create_device( self, id: str, @@ -276,17 +279,18 @@ def create_device( voip_token=voip_token, user=user, ) - return self.post("/api/v2/devices", Response, json=json) + @telemetry.operation_name("getstream.api.common.export_users") def export_users(self, user_ids: List[str]) -> StreamResponse[ExportUsersResponse]: json = build_body_dict(user_ids=user_ids) - return self.post("/api/v2/export/users", ExportUsersResponse, json=json) + @telemetry.operation_name("getstream.api.common.list_external_storage") def list_external_storage(self) -> StreamResponse[ListExternalStorageResponse]: return self.get("/api/v2/external_storage", ListExternalStorageResponse) + @telemetry.operation_name("getstream.api.common.create_external_storage") def create_external_storage( self, bucket: str, @@ -306,24 +310,24 @@ def create_external_storage( aws_s3=aws_s3, azure_blob=azure_blob, ) - return self.post( "/api/v2/external_storage", CreateExternalStorageResponse, json=json ) + @telemetry.operation_name("getstream.api.common.delete_external_storage") def delete_external_storage( self, name: str ) -> StreamResponse[DeleteExternalStorageResponse]: path_params = { "name": name, } - return self.delete( "/api/v2/external_storage/{name}", DeleteExternalStorageResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.common.update_external_storage") def update_external_storage( self, name: str, @@ -345,7 +349,6 @@ def update_external_storage( aws_s3=aws_s3, azure_blob=azure_blob, ) - return self.put( "/api/v2/external_storage/{name}", UpdateExternalStorageResponse, @@ -353,69 +356,72 @@ def update_external_storage( json=json, ) + @telemetry.operation_name("getstream.api.common.check_external_storage") def check_external_storage( self, name: str ) -> StreamResponse[CheckExternalStorageResponse]: path_params = { "name": name, } - return self.get( "/api/v2/external_storage/{name}/check", CheckExternalStorageResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.common.create_guest") def create_guest(self, user: UserRequest) -> StreamResponse[CreateGuestResponse]: json = build_body_dict(user=user) - return self.post("/api/v2/guest", CreateGuestResponse, json=json) + @telemetry.operation_name("getstream.api.common.create_import_url") def create_import_url( self, filename: Optional[str] = None ) -> StreamResponse[CreateImportURLResponse]: json = build_body_dict(filename=filename) - return self.post("/api/v2/import_urls", CreateImportURLResponse, json=json) + @telemetry.operation_name("getstream.api.common.list_imports") def list_imports(self) -> StreamResponse[ListImportsResponse]: return self.get("/api/v2/imports", ListImportsResponse) + @telemetry.operation_name("getstream.api.common.create_import") def create_import( self, mode: str, path: str ) -> StreamResponse[CreateImportResponse]: json = build_body_dict(mode=mode, path=path) - return self.post("/api/v2/imports", CreateImportResponse, json=json) + @telemetry.operation_name("getstream.api.common.get_import") def get_import(self, id: str) -> StreamResponse[GetImportResponse]: path_params = { "id": id, } - return self.get( "/api/v2/imports/{id}", GetImportResponse, path_params=path_params ) + @telemetry.operation_name("getstream.api.common.get_og") def get_og(self, url: str) -> StreamResponse[GetOGResponse]: query_params = build_query_param(url=url) - return self.get("/api/v2/og", GetOGResponse, query_params=query_params) + @telemetry.operation_name("getstream.api.common.list_permissions") def list_permissions(self) -> StreamResponse[ListPermissionsResponse]: return self.get("/api/v2/permissions", ListPermissionsResponse) + @telemetry.operation_name("getstream.api.common.get_permission") def get_permission(self, id: str) -> StreamResponse[GetCustomPermissionResponse]: path_params = { "id": id, } - return self.get( "/api/v2/permissions/{id}", GetCustomPermissionResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.common.create_poll") def create_poll( self, name: str, @@ -447,9 +453,9 @@ def create_poll( custom=custom, user=user, ) - return self.post("/api/v2/polls", PollResponse, json=json) + @telemetry.operation_name("getstream.api.common.update_poll") def update_poll( self, id: str, @@ -481,9 +487,9 @@ def update_poll( custom=custom, user=user, ) - return self.put("/api/v2/polls", PollResponse, json=json) + @telemetry.operation_name("getstream.api.common.query_polls") def query_polls( self, user_id: Optional[str] = None, @@ -497,7 +503,6 @@ def query_polls( json = build_body_dict( limit=limit, next=next, prev=prev, sort=sort, filter=filter ) - return self.post( "/api/v2/polls/query", QueryPollsResponse, @@ -505,6 +510,7 @@ def query_polls( json=json, ) + @telemetry.operation_name("getstream.api.common.delete_poll") def delete_poll( self, poll_id: str, user_id: Optional[str] = None ) -> StreamResponse[Response]: @@ -512,7 +518,6 @@ def delete_poll( path_params = { "poll_id": poll_id, } - return self.delete( "/api/v2/polls/{poll_id}", Response, @@ -520,6 +525,7 @@ def delete_poll( path_params=path_params, ) + @telemetry.operation_name("getstream.api.common.get_poll") def get_poll( self, poll_id: str, user_id: Optional[str] = None ) -> StreamResponse[PollResponse]: @@ -527,7 +533,6 @@ def get_poll( path_params = { "poll_id": poll_id, } - return self.get( "/api/v2/polls/{poll_id}", PollResponse, @@ -535,6 +540,7 @@ def get_poll( path_params=path_params, ) + @telemetry.operation_name("getstream.api.common.update_poll_partial") def update_poll_partial( self, poll_id: str, @@ -547,11 +553,11 @@ def update_poll_partial( "poll_id": poll_id, } json = build_body_dict(user_id=user_id, unset=unset, set=set, user=user) - return self.patch( "/api/v2/polls/{poll_id}", PollResponse, path_params=path_params, json=json ) + @telemetry.operation_name("getstream.api.common.create_poll_option") def create_poll_option( self, poll_id: str, @@ -564,7 +570,6 @@ def create_poll_option( "poll_id": poll_id, } json = build_body_dict(text=text, user_id=user_id, custom=custom, user=user) - return self.post( "/api/v2/polls/{poll_id}/options", PollOptionResponse, @@ -572,6 +577,7 @@ def create_poll_option( json=json, ) + @telemetry.operation_name("getstream.api.common.update_poll_option") def update_poll_option( self, poll_id: str, @@ -587,7 +593,6 @@ def update_poll_option( json = build_body_dict( id=id, text=text, user_id=user_id, custom=custom, user=user ) - return self.put( "/api/v2/polls/{poll_id}/options", PollOptionResponse, @@ -595,6 +600,7 @@ def update_poll_option( json=json, ) + @telemetry.operation_name("getstream.api.common.delete_poll_option") def delete_poll_option( self, poll_id: str, option_id: str, user_id: Optional[str] = None ) -> StreamResponse[Response]: @@ -603,7 +609,6 @@ def delete_poll_option( "poll_id": poll_id, "option_id": option_id, } - return self.delete( "/api/v2/polls/{poll_id}/options/{option_id}", Response, @@ -611,6 +616,7 @@ def delete_poll_option( path_params=path_params, ) + @telemetry.operation_name("getstream.api.common.get_poll_option") def get_poll_option( self, poll_id: str, option_id: str, user_id: Optional[str] = None ) -> StreamResponse[PollOptionResponse]: @@ -619,7 +625,6 @@ def get_poll_option( "poll_id": poll_id, "option_id": option_id, } - return self.get( "/api/v2/polls/{poll_id}/options/{option_id}", PollOptionResponse, @@ -627,6 +632,7 @@ def get_poll_option( path_params=path_params, ) + @telemetry.operation_name("getstream.api.common.query_poll_votes") def query_poll_votes( self, poll_id: str, @@ -644,7 +650,6 @@ def query_poll_votes( json = build_body_dict( limit=limit, next=next, prev=prev, sort=sort, filter=filter ) - return self.post( "/api/v2/polls/{poll_id}/votes", PollVotesResponse, @@ -653,50 +658,54 @@ def query_poll_votes( json=json, ) + @telemetry.operation_name( + "getstream.api.common.update_push_notification_preferences" + ) def update_push_notification_preferences( self, preferences: List[PushPreferenceInput] ) -> StreamResponse[UpsertPushPreferencesResponse]: json = build_body_dict(preferences=preferences) - return self.post( "/api/v2/push_preferences", UpsertPushPreferencesResponse, json=json ) + @telemetry.operation_name("getstream.api.common.list_push_providers") def list_push_providers(self) -> StreamResponse[ListPushProvidersResponse]: return self.get("/api/v2/push_providers", ListPushProvidersResponse) + @telemetry.operation_name("getstream.api.common.upsert_push_provider") def upsert_push_provider( self, push_provider: Optional[PushProvider] = None ) -> StreamResponse[UpsertPushProviderResponse]: json = build_body_dict(push_provider=push_provider) - return self.post( "/api/v2/push_providers", UpsertPushProviderResponse, json=json ) + @telemetry.operation_name("getstream.api.common.delete_push_provider") def delete_push_provider(self, type: str, name: str) -> StreamResponse[Response]: path_params = { "type": type, "name": name, } - return self.delete( "/api/v2/push_providers/{type}/{name}", Response, path_params=path_params ) + @telemetry.operation_name("getstream.api.common.get_push_templates") def get_push_templates( self, push_provider_type: str, push_provider_name: Optional[str] = None ) -> StreamResponse[GetPushTemplatesResponse]: query_params = build_query_param( push_provider_type=push_provider_type, push_provider_name=push_provider_name ) - return self.get( "/api/v2/push_templates", GetPushTemplatesResponse, query_params=query_params, ) + @telemetry.operation_name("getstream.api.common.upsert_push_template") def upsert_push_template( self, event_type: str, @@ -712,11 +721,11 @@ def upsert_push_template( push_provider_name=push_provider_name, template=template, ) - return self.post( "/api/v2/push_templates", UpsertPushTemplateResponse, json=json ) + @telemetry.operation_name("getstream.api.common.get_rate_limits") def get_rate_limits( self, server_side: Optional[bool] = None, @@ -732,50 +741,51 @@ def get_rate_limits( web=web, endpoints=endpoints, ) - return self.get( "/api/v2/rate_limits", GetRateLimitsResponse, query_params=query_params ) + @telemetry.operation_name("getstream.api.common.list_roles") def list_roles(self) -> StreamResponse[ListRolesResponse]: return self.get("/api/v2/roles", ListRolesResponse) + @telemetry.operation_name("getstream.api.common.create_role") def create_role(self, name: str) -> StreamResponse[CreateRoleResponse]: json = build_body_dict(name=name) - return self.post("/api/v2/roles", CreateRoleResponse, json=json) + @telemetry.operation_name("getstream.api.common.delete_role") def delete_role(self, name: str) -> StreamResponse[Response]: path_params = { "name": name, } - return self.delete("/api/v2/roles/{name}", Response, path_params=path_params) + @telemetry.operation_name("getstream.api.common.get_task") def get_task(self, id: str) -> StreamResponse[GetTaskResponse]: path_params = { "id": id, } - return self.get("/api/v2/tasks/{id}", GetTaskResponse, path_params=path_params) + @telemetry.operation_name("getstream.api.common.delete_file") def delete_file(self, url: Optional[str] = None) -> StreamResponse[Response]: query_params = build_query_param(url=url) - return self.delete("/api/v2/uploads/file", Response, query_params=query_params) + @telemetry.operation_name("getstream.api.common.upload_file") def upload_file( self, file: Optional[str] = None, user: Optional[OnlyUserID] = None ) -> StreamResponse[FileUploadResponse]: json = build_body_dict(file=file, user=user) - return self.post("/api/v2/uploads/file", FileUploadResponse, json=json) + @telemetry.operation_name("getstream.api.common.delete_image") def delete_image(self, url: Optional[str] = None) -> StreamResponse[Response]: query_params = build_query_param(url=url) - return self.delete("/api/v2/uploads/image", Response, query_params=query_params) + @telemetry.operation_name("getstream.api.common.upload_image") def upload_image( self, file: Optional[str] = None, @@ -783,39 +793,39 @@ def upload_image( user: Optional[OnlyUserID] = None, ) -> StreamResponse[ImageUploadResponse]: json = build_body_dict(file=file, upload_sizes=upload_sizes, user=user) - return self.post("/api/v2/uploads/image", ImageUploadResponse, json=json) + @telemetry.operation_name("getstream.api.common.query_users") def query_users( self, payload: Optional[QueryUsersPayload] = None ) -> StreamResponse[QueryUsersResponse]: query_params = build_query_param(payload=payload) - return self.get("/api/v2/users", QueryUsersResponse, query_params=query_params) + @telemetry.operation_name("getstream.api.common.update_users_partial") def update_users_partial( self, users: List[UpdateUserPartialRequest] ) -> StreamResponse[UpdateUsersResponse]: json = build_body_dict(users=users) - return self.patch("/api/v2/users", UpdateUsersResponse, json=json) + @telemetry.operation_name("getstream.api.common.update_users") def update_users( self, users: Dict[str, UserRequest] ) -> StreamResponse[UpdateUsersResponse]: json = build_body_dict(users=users) - return self.post("/api/v2/users", UpdateUsersResponse, json=json) + @telemetry.operation_name("getstream.api.common.get_blocked_users") def get_blocked_users( self, user_id: Optional[str] = None ) -> StreamResponse[GetBlockedUsersResponse]: query_params = build_query_param(user_id=user_id) - return self.get( "/api/v2/users/block", GetBlockedUsersResponse, query_params=query_params ) + @telemetry.operation_name("getstream.api.common.block_users") def block_users( self, blocked_user_id: str, @@ -825,9 +835,9 @@ def block_users( json = build_body_dict( blocked_user_id=blocked_user_id, user_id=user_id, user=user ) - return self.post("/api/v2/users/block", BlockUsersResponse, json=json) + @telemetry.operation_name("getstream.api.common.deactivate_users") def deactivate_users( self, user_ids: List[str], @@ -841,9 +851,9 @@ def deactivate_users( mark_channels_deleted=mark_channels_deleted, mark_messages_deleted=mark_messages_deleted, ) - return self.post("/api/v2/users/deactivate", DeactivateUsersResponse, json=json) + @telemetry.operation_name("getstream.api.common.delete_users") def delete_users( self, user_ids: List[str], @@ -865,20 +875,20 @@ def delete_users( new_channel_owner_id=new_channel_owner_id, user=user, ) - return self.post("/api/v2/users/delete", DeleteUsersResponse, json=json) + @telemetry.operation_name("getstream.api.common.get_user_live_locations") def get_user_live_locations( self, user_id: Optional[str] = None ) -> StreamResponse[SharedLocationsResponse]: query_params = build_query_param(user_id=user_id) - return self.get( "/api/v2/users/live_locations", SharedLocationsResponse, query_params=query_params, ) + @telemetry.operation_name("getstream.api.common.update_live_location") def update_live_location( self, message_id: str, @@ -891,7 +901,6 @@ def update_live_location( json = build_body_dict( message_id=message_id, end_at=end_at, latitude=latitude, longitude=longitude ) - return self.put( "/api/v2/users/live_locations", SharedLocationResponse, @@ -899,6 +908,7 @@ def update_live_location( json=json, ) + @telemetry.operation_name("getstream.api.common.reactivate_users") def reactivate_users( self, user_ids: List[str], @@ -912,14 +922,14 @@ def reactivate_users( restore_channels=restore_channels, restore_messages=restore_messages, ) - return self.post("/api/v2/users/reactivate", ReactivateUsersResponse, json=json) + @telemetry.operation_name("getstream.api.common.restore_users") def restore_users(self, user_ids: List[str]) -> StreamResponse[Response]: json = build_body_dict(user_ids=user_ids) - return self.post("/api/v2/users/restore", Response, json=json) + @telemetry.operation_name("getstream.api.common.unblock_users") def unblock_users( self, blocked_user_id: str, @@ -929,9 +939,9 @@ def unblock_users( json = build_body_dict( blocked_user_id=blocked_user_id, user_id=user_id, user=user ) - return self.post("/api/v2/users/unblock", UnblockUsersResponse, json=json) + @telemetry.operation_name("getstream.api.common.deactivate_user") def deactivate_user( self, user_id: str, @@ -944,7 +954,6 @@ def deactivate_user( json = build_body_dict( created_by_id=created_by_id, mark_messages_deleted=mark_messages_deleted ) - return self.post( "/api/v2/users/{user_id}/deactivate", DeactivateUserResponse, @@ -952,17 +961,18 @@ def deactivate_user( json=json, ) + @telemetry.operation_name("getstream.api.common.export_user") def export_user(self, user_id: str) -> StreamResponse[ExportUserResponse]: path_params = { "user_id": user_id, } - return self.get( "/api/v2/users/{user_id}/export", ExportUserResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.common.reactivate_user") def reactivate_user( self, user_id: str, @@ -976,7 +986,6 @@ def reactivate_user( json = build_body_dict( created_by_id=created_by_id, name=name, restore_messages=restore_messages ) - return self.post( "/api/v2/users/{user_id}/reactivate", ReactivateUserResponse, diff --git a/getstream/models/__init__.py b/getstream/models/__init__.py index 388febf0..a3593ee6 100644 --- a/getstream/models/__init__.py +++ b/getstream/models/__init__.py @@ -1043,6 +1043,9 @@ class AddCommentReactionRequest(DataClassJsonMixin): create_notification_activity: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="create_notification_activity") ) + enforce_unique: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="enforce_unique") + ) skip_push: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="skip_push") ) @@ -1132,6 +1135,9 @@ class AddReactionRequest(DataClassJsonMixin): create_notification_activity: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="create_notification_activity") ) + enforce_unique: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="enforce_unique") + ) skip_push: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="skip_push") ) @@ -1177,6 +1183,9 @@ class AggregatedActivityResponse(DataClassJsonMixin): ) ) user_count: int = dc_field(metadata=dc_config(field_name="user_count")) + user_count_truncated: bool = dc_field( + metadata=dc_config(field_name="user_count_truncated") + ) activities: "List[ActivityResponse]" = dc_field( metadata=dc_config(field_name="activities") ) @@ -1450,7 +1459,8 @@ class AsyncExportErrorEvent(DataClassJsonMixin): task_id: str = dc_field(metadata=dc_config(field_name="task_id")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( - default="export.channels.error", metadata=dc_config(field_name="type") + default="export.bulk_image_moderation.error", + metadata=dc_config(field_name="type"), ) received_at: Optional[datetime] = dc_field( default=None, @@ -1858,6 +1868,9 @@ class BanActionRequest(DataClassJsonMixin): @dataclass class BanOptions(DataClassJsonMixin): + delete_messages: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="delete_messages") + ) duration: Optional[int] = dc_field( default=None, metadata=dc_config(field_name="duration") ) @@ -1937,13 +1950,6 @@ class BanResponse(DataClassJsonMixin): ) -@dataclass -class BlockContentOptions(DataClassJsonMixin): - reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="reason") - ) - - @dataclass class BlockListConfig(DataClassJsonMixin): _async: Optional[bool] = dc_field( @@ -3918,6 +3924,71 @@ class CallStateResponseFields(DataClassJsonMixin): call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) +@dataclass +class CallStatsParticipant(DataClassJsonMixin): + user_id: str = dc_field(metadata=dc_config(field_name="user_id")) + sessions: "List[CallStatsParticipantSession]" = dc_field( + metadata=dc_config(field_name="sessions") + ) + latest_activity_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="latest_activity_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + name: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="name")) + roles: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="roles") + ) + + +@dataclass +class CallStatsParticipantCounts(DataClassJsonMixin): + live_sessions: int = dc_field(metadata=dc_config(field_name="live_sessions")) + participants: int = dc_field(metadata=dc_config(field_name="participants")) + publishers: int = dc_field(metadata=dc_config(field_name="publishers")) + sessions: int = dc_field(metadata=dc_config(field_name="sessions")) + + +@dataclass +class CallStatsParticipantSession(DataClassJsonMixin): + is_live: bool = dc_field(metadata=dc_config(field_name="is_live")) + user_session_id: str = dc_field(metadata=dc_config(field_name="user_session_id")) + published_tracks: "PublishedTrackFlags" = dc_field( + metadata=dc_config(field_name="published_tracks") + ) + cq_score: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="cq_score") + ) + ended_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="ended_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + publisher_type: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="publisher_type") + ) + started_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="started_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + unified_session_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="unified_session_id") + ) + + @dataclass class CallStatsReportReadyEvent(DataClassJsonMixin): call_cid: str = dc_field(metadata=dc_config(field_name="call_cid")) @@ -4185,6 +4256,7 @@ class CallUserMutedEvent(DataClassJsonMixin): ) ) from_user_id: str = dc_field(metadata=dc_config(field_name="from_user_id")) + reason: str = dc_field(metadata=dc_config(field_name="reason")) muted_user_ids: List[str] = dc_field( metadata=dc_config(field_name="muted_user_ids") ) @@ -4270,6 +4342,9 @@ class CampaignResponse(DataClassJsonMixin): name: str = dc_field(metadata=dc_config(field_name="name")) sender_id: str = dc_field(metadata=dc_config(field_name="sender_id")) sender_mode: str = dc_field(metadata=dc_config(field_name="sender_mode")) + sender_visibility: str = dc_field( + metadata=dc_config(field_name="sender_visibility") + ) show_channels: bool = dc_field(metadata=dc_config(field_name="show_channels")) skip_push: bool = dc_field(metadata=dc_config(field_name="skip_push")) skip_webhook: bool = dc_field(metadata=dc_config(field_name="skip_webhook")) @@ -6078,6 +6153,9 @@ class ConfigResponse(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + supported_video_call_harm_types: List[str] = dc_field( + metadata=dc_config(field_name="supported_video_call_harm_types") + ) ai_image_config: "Optional[AIImageConfig]" = dc_field( default=None, metadata=dc_config(field_name="ai_image_config") ) @@ -6942,6 +7020,7 @@ class DeleteCommentReactionResponse(DataClassJsonMixin): @dataclass class DeleteCommentResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) + activity: "ActivityResponse" = dc_field(metadata=dc_config(field_name="activity")) comment: "CommentResponse" = dc_field(metadata=dc_config(field_name="comment")) @@ -8250,35 +8329,42 @@ class FeedMemberUpdatedEvent(DataClassJsonMixin): class FeedOwnCapability: ADD_ACTIVITY: Final[FeedOwnCapabilityType] = "add-activity" + ADD_ACTIVITY_BOOKMARK: Final[FeedOwnCapabilityType] = "add-activity-bookmark" ADD_ACTIVITY_REACTION: Final[FeedOwnCapabilityType] = "add-activity-reaction" ADD_COMMENT: Final[FeedOwnCapabilityType] = "add-comment" ADD_COMMENT_REACTION: Final[FeedOwnCapabilityType] = "add-comment-reaction" - BOOKMARK_ACTIVITY: Final[FeedOwnCapabilityType] = "bookmark-activity" CREATE_FEED: Final[FeedOwnCapabilityType] = "create-feed" - DELETE_BOOKMARK: Final[FeedOwnCapabilityType] = "delete-bookmark" - DELETE_COMMENT: Final[FeedOwnCapabilityType] = "delete-comment" + DELETE_ANY_ACTIVITY: Final[FeedOwnCapabilityType] = "delete-any-activity" + DELETE_ANY_COMMENT: Final[FeedOwnCapabilityType] = "delete-any-comment" DELETE_FEED: Final[FeedOwnCapabilityType] = "delete-feed" - EDIT_BOOKMARK: Final[FeedOwnCapabilityType] = "edit-bookmark" + DELETE_OWN_ACTIVITY: Final[FeedOwnCapabilityType] = "delete-own-activity" + DELETE_OWN_ACTIVITY_BOOKMARK: Final[FeedOwnCapabilityType] = ( + "delete-own-activity-bookmark" + ) + DELETE_OWN_ACTIVITY_REACTION: Final[FeedOwnCapabilityType] = ( + "delete-own-activity-reaction" + ) + DELETE_OWN_COMMENT: Final[FeedOwnCapabilityType] = "delete-own-comment" + DELETE_OWN_COMMENT_REACTION: Final[FeedOwnCapabilityType] = ( + "delete-own-comment-reaction" + ) FOLLOW: Final[FeedOwnCapabilityType] = "follow" - INVITE_FEED: Final[FeedOwnCapabilityType] = "invite-feed" - JOIN_FEED: Final[FeedOwnCapabilityType] = "join-feed" - LEAVE_FEED: Final[FeedOwnCapabilityType] = "leave-feed" - MANAGE_FEED_GROUP: Final[FeedOwnCapabilityType] = "manage-feed-group" - MARK_ACTIVITY: Final[FeedOwnCapabilityType] = "mark-activity" PIN_ACTIVITY: Final[FeedOwnCapabilityType] = "pin-activity" QUERY_FEED_MEMBERS: Final[FeedOwnCapabilityType] = "query-feed-members" QUERY_FOLLOWS: Final[FeedOwnCapabilityType] = "query-follows" READ_ACTIVITIES: Final[FeedOwnCapabilityType] = "read-activities" READ_FEED: Final[FeedOwnCapabilityType] = "read-feed" - REMOVE_ACTIVITY: Final[FeedOwnCapabilityType] = "remove-activity" - REMOVE_ACTIVITY_REACTION: Final[FeedOwnCapabilityType] = "remove-activity-reaction" - REMOVE_COMMENT_REACTION: Final[FeedOwnCapabilityType] = "remove-comment-reaction" UNFOLLOW: Final[FeedOwnCapabilityType] = "unfollow" - UPDATE_ACTIVITY: Final[FeedOwnCapabilityType] = "update-activity" - UPDATE_COMMENT: Final[FeedOwnCapabilityType] = "update-comment" + UPDATE_ANY_ACTIVITY: Final[FeedOwnCapabilityType] = "update-any-activity" + UPDATE_ANY_COMMENT: Final[FeedOwnCapabilityType] = "update-any-comment" UPDATE_FEED: Final[FeedOwnCapabilityType] = "update-feed" UPDATE_FEED_FOLLOWERS: Final[FeedOwnCapabilityType] = "update-feed-followers" UPDATE_FEED_MEMBERS: Final[FeedOwnCapabilityType] = "update-feed-members" + UPDATE_OWN_ACTIVITY: Final[FeedOwnCapabilityType] = "update-own-activity" + UPDATE_OWN_ACTIVITY_BOOKMARK: Final[FeedOwnCapabilityType] = ( + "update-own-activity-bookmark" + ) + UPDATE_OWN_COMMENT: Final[FeedOwnCapabilityType] = "update-own-comment" @dataclass @@ -8349,12 +8435,18 @@ class FeedResponse(DataClassJsonMixin): filter_tags: Optional[List[str]] = dc_field( default=None, metadata=dc_config(field_name="filter_tags") ) + own_capabilities: "Optional[List[FeedOwnCapability]]" = dc_field( + default=None, metadata=dc_config(field_name="own_capabilities") + ) own_follows: "Optional[List[FollowResponse]]" = dc_field( default=None, metadata=dc_config(field_name="own_follows") ) custom: Optional[Dict[str, object]] = dc_field( default=None, metadata=dc_config(field_name="custom") ) + own_membership: "Optional[FeedMemberResponse]" = dc_field( + default=None, metadata=dc_config(field_name="own_membership") + ) @dataclass @@ -8416,6 +8508,13 @@ class FeedViewResponse(DataClassJsonMixin): ) +@dataclass +class FeedVisibilityResponse(DataClassJsonMixin): + description: str = dc_field(metadata=dc_config(field_name="description")) + name: str = dc_field(metadata=dc_config(field_name="name")) + grants: "Dict[str, List[str]]" = dc_field(metadata=dc_config(field_name="grants")) + + @dataclass class FeedsModerationTemplateConfig(DataClassJsonMixin): config_key: str = dc_field(metadata=dc_config(field_name="config_key")) @@ -8566,9 +8665,8 @@ class Flag(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) - created_by_automod: bool = dc_field( - metadata=dc_config(field_name="created_by_automod") - ) + entity_id: str = dc_field(metadata=dc_config(field_name="entity_id")) + entity_type: str = dc_field(metadata=dc_config(field_name="entity_type")) updated_at: datetime = dc_field( metadata=dc_config( field_name="updated_at", @@ -8577,66 +8675,42 @@ class Flag(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) - approved_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="approved_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), + result: "List[Dict[str, object]]" = dc_field( + metadata=dc_config(field_name="result") ) - reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="reason") + entity_creator_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="entity_creator_id") ) - rejected_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="rejected_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), + is_streamed_content: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="is_streamed_content") ) - reviewed_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="reviewed_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), + moderation_payload_hash: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="moderation_payload_hash") ) - reviewed_by: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="reviewed_by") + reason: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="reason") + ) + review_queue_item_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="review_queue_item_id") ) - target_message_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="target_message_id") + type: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="type")) + labels: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="labels") ) custom: Optional[Dict[str, object]] = dc_field( default=None, metadata=dc_config(field_name="custom") ) - details: "Optional[FlagDetails]" = dc_field( - default=None, metadata=dc_config(field_name="details") - ) - target_message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="target_message") + moderation_payload: "Optional[ModerationPayload]" = dc_field( + default=None, metadata=dc_config(field_name="moderation_payload") ) - target_user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="target_user") + review_queue_item: "Optional[ReviewQueueItem]" = dc_field( + default=None, metadata=dc_config(field_name="review_queue_item") ) user: "Optional[User]" = dc_field( default=None, metadata=dc_config(field_name="user") ) -@dataclass -class FlagContentOptions(DataClassJsonMixin): - reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="reason") - ) - - @dataclass class FlagDetails(DataClassJsonMixin): original_text: str = dc_field(metadata=dc_config(field_name="original_text")) @@ -9400,6 +9474,14 @@ class GetFeedViewResponse(DataClassJsonMixin): feed_view: "FeedViewResponse" = dc_field(metadata=dc_config(field_name="feed_view")) +@dataclass +class GetFeedVisibilityResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + feed_visibility: "FeedVisibilityResponse" = dc_field( + metadata=dc_config(field_name="feed_visibility") + ) + + @dataclass class GetFollowSuggestionsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) @@ -9635,18 +9717,12 @@ class GetOrCreateFeedResponse(DataClassJsonMixin): members: "List[FeedMemberResponse]" = dc_field( metadata=dc_config(field_name="members") ) - own_capabilities: "List[FeedOwnCapability]" = dc_field( - metadata=dc_config(field_name="own_capabilities") - ) pinned_activities: "List[ActivityPinResponse]" = dc_field( metadata=dc_config(field_name="pinned_activities") ) feed: "FeedResponse" = dc_field(metadata=dc_config(field_name="feed")) next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) - own_follows: "Optional[List[FollowResponse]]" = dc_field( - default=None, metadata=dc_config(field_name="own_follows") - ) followers_pagination: "Optional[PagerResponse]" = dc_field( default=None, metadata=dc_config(field_name="followers_pagination") ) @@ -9659,9 +9735,6 @@ class GetOrCreateFeedResponse(DataClassJsonMixin): notification_status: "Optional[NotificationStatusResponse]" = dc_field( default=None, metadata=dc_config(field_name="notification_status") ) - own_membership: "Optional[FeedMemberResponse]" = dc_field( - default=None, metadata=dc_config(field_name="own_membership") - ) @dataclass @@ -9874,12 +9947,21 @@ class HLSSettingsResponse(DataClassJsonMixin): @dataclass class HarmConfig(DataClassJsonMixin): + cooldown_period: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="cooldown_period") + ) severity: Optional[int] = dc_field( default=None, metadata=dc_config(field_name="severity") ) + threshold: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="threshold") + ) action_sequences: "Optional[List[ActionSequence]]" = dc_field( default=None, metadata=dc_config(field_name="action_sequences") ) + harm_types: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="harm_types") + ) @dataclass @@ -10411,6 +10493,14 @@ class ListFeedViewsResponse(DataClassJsonMixin): ) +@dataclass +class ListFeedVisibilitiesResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + feed_visibilities: "Dict[str, FeedVisibilityResponse]" = dc_field( + metadata=dc_config(field_name="feed_visibilities") + ) + + @dataclass class ListImportsResponse(DataClassJsonMixin): duration: str = dc_field(metadata=dc_config(field_name="duration")) @@ -11086,9 +11176,7 @@ class MessageNewEvent(DataClassJsonMixin): ) ) watcher_count: int = dc_field(metadata=dc_config(field_name="watcher_count")) - type: str = dc_field( - default="notification.thread_message_new", metadata=dc_config(field_name="type") - ) + type: str = dc_field(default="message.new", metadata=dc_config(field_name="type")) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) thread_participants: "Optional[List[User]]" = dc_field( default=None, metadata=dc_config(field_name="thread_participants") @@ -11718,6 +11806,9 @@ class ModerationConfig(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ), ) + supported_video_call_harm_types: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="supported_video_call_harm_types") + ) ai_image_config: "Optional[AIImageConfig]" = dc_field( default=None, metadata=dc_config(field_name="ai_image_config") ) @@ -11786,6 +11877,9 @@ class ModerationCustomActionEvent(DataClassJsonMixin): @dataclass class ModerationDashboardPreferences(DataClassJsonMixin): + disable_flagging_reviewed_entity: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="disable_flagging_reviewed_entity") + ) flag_user_on_flagged_content: Optional[bool] = dc_field( default=None, metadata=dc_config(field_name="flag_user_on_flagged_content") ) @@ -13045,6 +13139,16 @@ class PrivacySettingsResponse(DataClassJsonMixin): ) +@dataclass +class PublishedTrackFlags(DataClassJsonMixin): + audio: bool = dc_field(metadata=dc_config(field_name="audio")) + screenshare: bool = dc_field(metadata=dc_config(field_name="screenshare")) + screenshare_audio: bool = dc_field( + metadata=dc_config(field_name="screenshare_audio") + ) + video: bool = dc_field(metadata=dc_config(field_name="video")) + + @dataclass class PublisherAllMetrics(DataClassJsonMixin): audio: "Optional[PublisherAudioMetrics]" = dc_field( @@ -13680,6 +13784,25 @@ class QueryCallParticipantsResponse(DataClassJsonMixin): call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) +@dataclass +class QueryCallSessionParticipantStatsResponse(DataClassJsonMixin): + call_id: str = dc_field(metadata=dc_config(field_name="call_id")) + call_session_id: str = dc_field(metadata=dc_config(field_name="call_session_id")) + call_type: str = dc_field(metadata=dc_config(field_name="call_type")) + duration: str = dc_field(metadata=dc_config(field_name="duration")) + participants: "List[CallStatsParticipant]" = dc_field( + metadata=dc_config(field_name="participants") + ) + counts: "CallStatsParticipantCounts" = dc_field( + metadata=dc_config(field_name="counts") + ) + next: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="next")) + prev: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="prev")) + tmp_data_source: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="tmp_data_source") + ) + + @dataclass class QueryCallStatsRequest(DataClassJsonMixin): limit: Optional[int] = dc_field( @@ -14573,10 +14696,10 @@ class RTMPSettingsResponse(DataClassJsonMixin): @dataclass class RankingConfig(DataClassJsonMixin): + type: str = dc_field(metadata=dc_config(field_name="type")) score: Optional[str] = dc_field( default=None, metadata=dc_config(field_name="score") ) - type: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="type")) defaults: Optional[Dict[str, object]] = dc_field( default=None, metadata=dc_config(field_name="defaults") ) @@ -15469,15 +15592,9 @@ class RuleBuilderAction(DataClassJsonMixin): ban_options: "Optional[BanOptions]" = dc_field( default=None, metadata=dc_config(field_name="ban_options") ) - flag_content_options: "Optional[FlagContentOptions]" = dc_field( - default=None, metadata=dc_config(field_name="flag_content_options") - ) flag_user_options: "Optional[FlagUserOptions]" = dc_field( default=None, metadata=dc_config(field_name="flag_user_options") ) - remove_content_options: "Optional[BlockContentOptions]" = dc_field( - default=None, metadata=dc_config(field_name="remove_content_options") - ) @dataclass @@ -19577,7 +19694,13 @@ class VelocityFilterConfigRule(DataClassJsonMixin): @dataclass class VideoCallRuleConfig(DataClassJsonMixin): - rules: "Optional[Dict[str, HarmConfig]]" = dc_field( + flag_all_labels: Optional[bool] = dc_field( + default=None, metadata=dc_config(field_name="flag_all_labels") + ) + flagged_labels: Optional[List[str]] = dc_field( + default=None, metadata=dc_config(field_name="flagged_labels") + ) + rules: "Optional[List[HarmConfig]]" = dc_field( default=None, metadata=dc_config(field_name="rules") ) diff --git a/getstream/moderation/async_rest_client.py b/getstream/moderation/async_rest_client.py index 98fd32dd..460c8c0b 100644 --- a/getstream/moderation/async_rest_client.py +++ b/getstream/moderation/async_rest_client.py @@ -1,5 +1,6 @@ # Code generated by GetStream internal OpenAPI code generator. DO NOT EDIT. from getstream.base import AsyncBaseClient +from getstream.common import telemetry from getstream.models import * from getstream.stream_response import StreamResponse from getstream.utils import build_query_param, build_body_dict @@ -21,6 +22,7 @@ def __init__(self, api_key: str, base_url: str, timeout: float, token: str): token=token, ) + @telemetry.operation_name("getstream.api.moderation.ban") async def ban( self, target_user_id: str, @@ -44,20 +46,20 @@ async def ban( timeout=timeout, banned_by=banned_by, ) - return await self.post("/api/v2/moderation/ban", BanResponse, json=json) + @telemetry.operation_name("getstream.api.moderation.bulk_image_moderation") async def bulk_image_moderation( self, csv_file: str ) -> StreamResponse[BulkImageModerationResponse]: json = build_body_dict(csv_file=csv_file) - return await self.post( "/api/v2/moderation/bulk_image_moderation", BulkImageModerationResponse, json=json, ) + @telemetry.operation_name("getstream.api.moderation.check") async def check( self, entity_creator_id: str, @@ -85,9 +87,9 @@ async def check( options=options, user=user, ) - return await self.post("/api/v2/moderation/check", CheckResponse, json=json) + @telemetry.operation_name("getstream.api.moderation.upsert_config") async def upsert_config( self, key: str, @@ -133,11 +135,11 @@ async def upsert_config( velocity_filter_config=velocity_filter_config, video_call_rule_config=video_call_rule_config, ) - return await self.post( "/api/v2/moderation/config", UpsertConfigResponse, json=json ) + @telemetry.operation_name("getstream.api.moderation.delete_config") async def delete_config( self, key: str, team: Optional[str] = None ) -> StreamResponse[DeleteModerationConfigResponse]: @@ -145,7 +147,6 @@ async def delete_config( path_params = { "key": key, } - return await self.delete( "/api/v2/moderation/config/{key}", DeleteModerationConfigResponse, @@ -153,6 +154,7 @@ async def delete_config( path_params=path_params, ) + @telemetry.operation_name("getstream.api.moderation.get_config") async def get_config( self, key: str, team: Optional[str] = None ) -> StreamResponse[GetConfigResponse]: @@ -160,7 +162,6 @@ async def get_config( path_params = { "key": key, } - return await self.get( "/api/v2/moderation/config/{key}", GetConfigResponse, @@ -168,6 +169,7 @@ async def get_config( path_params=path_params, ) + @telemetry.operation_name("getstream.api.moderation.query_moderation_configs") async def query_moderation_configs( self, limit: Optional[int] = None, @@ -187,11 +189,11 @@ async def query_moderation_configs( filter=filter, user=user, ) - return await self.post( "/api/v2/moderation/configs", QueryModerationConfigsResponse, json=json ) + @telemetry.operation_name("getstream.api.moderation.custom_check") async def custom_check( self, entity_id: str, @@ -211,11 +213,11 @@ async def custom_check( moderation_payload=moderation_payload, user=user, ) - return await self.post( "/api/v2/moderation/custom_check", CustomCheckResponse, json=json ) + @telemetry.operation_name("getstream.api.moderation.v2_delete_template") async def v2_delete_template( self, ) -> StreamResponse[DeleteModerationTemplateResponse]: @@ -224,6 +226,7 @@ async def v2_delete_template( DeleteModerationTemplateResponse, ) + @telemetry.operation_name("getstream.api.moderation.v2_query_templates") async def v2_query_templates( self, ) -> StreamResponse[QueryFeedModerationTemplatesResponse]: @@ -232,17 +235,18 @@ async def v2_query_templates( QueryFeedModerationTemplatesResponse, ) + @telemetry.operation_name("getstream.api.moderation.v2_upsert_template") async def v2_upsert_template( self, name: str, config: FeedsModerationTemplateConfig ) -> StreamResponse[UpsertModerationTemplateResponse]: json = build_body_dict(name=name, config=config) - return await self.post( "/api/v2/moderation/feeds_moderation_template", UpsertModerationTemplateResponse, json=json, ) + @telemetry.operation_name("getstream.api.moderation.flag") async def flag( self, entity_id: str, @@ -264,9 +268,9 @@ async def flag( moderation_payload=moderation_payload, user=user, ) - return await self.post("/api/v2/moderation/flag", FlagResponse, json=json) + @telemetry.operation_name("getstream.api.moderation.query_moderation_flags") async def query_moderation_flags( self, limit: Optional[int] = None, @@ -278,11 +282,11 @@ async def query_moderation_flags( json = build_body_dict( limit=limit, next=next, prev=prev, sort=sort, filter=filter ) - return await self.post( "/api/v2/moderation/flags", QueryModerationFlagsResponse, json=json ) + @telemetry.operation_name("getstream.api.moderation.query_moderation_logs") async def query_moderation_logs( self, limit: Optional[int] = None, @@ -302,11 +306,11 @@ async def query_moderation_logs( filter=filter, user=user, ) - return await self.post( "/api/v2/moderation/logs", QueryModerationLogsResponse, json=json ) + @telemetry.operation_name("getstream.api.moderation.upsert_moderation_rule") async def upsert_moderation_rule( self, name: str, @@ -334,13 +338,13 @@ async def upsert_moderation_rule( config_keys=config_keys, groups=groups, ) - return await self.post( "/api/v2/moderation/moderation_rule", UpsertModerationRuleResponse, json=json, ) + @telemetry.operation_name("getstream.api.moderation.delete_moderation_rule") async def delete_moderation_rule( self, ) -> StreamResponse[DeleteModerationRuleResponse]: @@ -348,11 +352,13 @@ async def delete_moderation_rule( "/api/v2/moderation/moderation_rule/{id}", DeleteModerationRuleResponse ) + @telemetry.operation_name("getstream.api.moderation.get_moderation_rule") async def get_moderation_rule(self) -> StreamResponse[GetModerationRuleResponse]: return await self.get( "/api/v2/moderation/moderation_rule/{id}", GetModerationRuleResponse ) + @telemetry.operation_name("getstream.api.moderation.query_moderation_rules") async def query_moderation_rules( self, limit: Optional[int] = None, @@ -372,13 +378,13 @@ async def query_moderation_rules( filter=filter, user=user, ) - return await self.post( "/api/v2/moderation/moderation_rules", QueryModerationRulesResponse, json=json, ) + @telemetry.operation_name("getstream.api.moderation.mute") async def mute( self, target_ids: List[str], @@ -389,9 +395,9 @@ async def mute( json = build_body_dict( target_ids=target_ids, timeout=timeout, user_id=user_id, user=user ) - return await self.post("/api/v2/moderation/mute", MuteResponse, json=json) + @telemetry.operation_name("getstream.api.moderation.query_review_queue") async def query_review_queue( self, limit: Optional[int] = None, @@ -419,24 +425,24 @@ async def query_review_queue( filter=filter, user=user, ) - return await self.post( "/api/v2/moderation/review_queue", QueryReviewQueueResponse, json=json ) + @telemetry.operation_name("getstream.api.moderation.get_review_queue_item") async def get_review_queue_item( self, id: str ) -> StreamResponse[GetReviewQueueItemResponse]: path_params = { "id": id, } - return await self.get( "/api/v2/moderation/review_queue/{id}", GetReviewQueueItemResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.moderation.submit_action") async def submit_action( self, action_type: str, @@ -466,11 +472,11 @@ async def submit_action( unban=unban, user=user, ) - return await self.post( "/api/v2/moderation/submit_action", SubmitActionResponse, json=json ) + @telemetry.operation_name("getstream.api.moderation.unban") async def unban( self, target_user_id: str, @@ -485,7 +491,6 @@ async def unban( created_by=created_by, ) json = build_body_dict(unbanned_by_id=unbanned_by_id, unbanned_by=unbanned_by) - return await self.post( "/api/v2/moderation/unban", UnbanResponse, @@ -493,6 +498,7 @@ async def unban( json=json, ) + @telemetry.operation_name("getstream.api.moderation.unmute") async def unmute( self, target_ids: List[str], @@ -500,5 +506,4 @@ async def unmute( user: Optional[UserRequest] = None, ) -> StreamResponse[UnmuteResponse]: json = build_body_dict(target_ids=target_ids, user_id=user_id, user=user) - return await self.post("/api/v2/moderation/unmute", UnmuteResponse, json=json) diff --git a/getstream/moderation/rest_client.py b/getstream/moderation/rest_client.py index d3daa1f8..82e773b6 100644 --- a/getstream/moderation/rest_client.py +++ b/getstream/moderation/rest_client.py @@ -1,5 +1,6 @@ # Code generated by GetStream internal OpenAPI code generator. DO NOT EDIT. from getstream.base import BaseClient +from getstream.common import telemetry from getstream.models import * from getstream.stream_response import StreamResponse from getstream.utils import build_query_param, build_body_dict @@ -21,6 +22,7 @@ def __init__(self, api_key: str, base_url: str, timeout: float, token: str): token=token, ) + @telemetry.operation_name("getstream.api.moderation.ban") def ban( self, target_user_id: str, @@ -44,20 +46,20 @@ def ban( timeout=timeout, banned_by=banned_by, ) - return self.post("/api/v2/moderation/ban", BanResponse, json=json) + @telemetry.operation_name("getstream.api.moderation.bulk_image_moderation") def bulk_image_moderation( self, csv_file: str ) -> StreamResponse[BulkImageModerationResponse]: json = build_body_dict(csv_file=csv_file) - return self.post( "/api/v2/moderation/bulk_image_moderation", BulkImageModerationResponse, json=json, ) + @telemetry.operation_name("getstream.api.moderation.check") def check( self, entity_creator_id: str, @@ -85,9 +87,9 @@ def check( options=options, user=user, ) - return self.post("/api/v2/moderation/check", CheckResponse, json=json) + @telemetry.operation_name("getstream.api.moderation.upsert_config") def upsert_config( self, key: str, @@ -133,9 +135,9 @@ def upsert_config( velocity_filter_config=velocity_filter_config, video_call_rule_config=video_call_rule_config, ) - return self.post("/api/v2/moderation/config", UpsertConfigResponse, json=json) + @telemetry.operation_name("getstream.api.moderation.delete_config") def delete_config( self, key: str, team: Optional[str] = None ) -> StreamResponse[DeleteModerationConfigResponse]: @@ -143,7 +145,6 @@ def delete_config( path_params = { "key": key, } - return self.delete( "/api/v2/moderation/config/{key}", DeleteModerationConfigResponse, @@ -151,6 +152,7 @@ def delete_config( path_params=path_params, ) + @telemetry.operation_name("getstream.api.moderation.get_config") def get_config( self, key: str, team: Optional[str] = None ) -> StreamResponse[GetConfigResponse]: @@ -158,7 +160,6 @@ def get_config( path_params = { "key": key, } - return self.get( "/api/v2/moderation/config/{key}", GetConfigResponse, @@ -166,6 +167,7 @@ def get_config( path_params=path_params, ) + @telemetry.operation_name("getstream.api.moderation.query_moderation_configs") def query_moderation_configs( self, limit: Optional[int] = None, @@ -185,11 +187,11 @@ def query_moderation_configs( filter=filter, user=user, ) - return self.post( "/api/v2/moderation/configs", QueryModerationConfigsResponse, json=json ) + @telemetry.operation_name("getstream.api.moderation.custom_check") def custom_check( self, entity_id: str, @@ -209,17 +211,18 @@ def custom_check( moderation_payload=moderation_payload, user=user, ) - return self.post( "/api/v2/moderation/custom_check", CustomCheckResponse, json=json ) + @telemetry.operation_name("getstream.api.moderation.v2_delete_template") def v2_delete_template(self) -> StreamResponse[DeleteModerationTemplateResponse]: return self.delete( "/api/v2/moderation/feeds_moderation_template", DeleteModerationTemplateResponse, ) + @telemetry.operation_name("getstream.api.moderation.v2_query_templates") def v2_query_templates( self, ) -> StreamResponse[QueryFeedModerationTemplatesResponse]: @@ -228,17 +231,18 @@ def v2_query_templates( QueryFeedModerationTemplatesResponse, ) + @telemetry.operation_name("getstream.api.moderation.v2_upsert_template") def v2_upsert_template( self, name: str, config: FeedsModerationTemplateConfig ) -> StreamResponse[UpsertModerationTemplateResponse]: json = build_body_dict(name=name, config=config) - return self.post( "/api/v2/moderation/feeds_moderation_template", UpsertModerationTemplateResponse, json=json, ) + @telemetry.operation_name("getstream.api.moderation.flag") def flag( self, entity_id: str, @@ -260,9 +264,9 @@ def flag( moderation_payload=moderation_payload, user=user, ) - return self.post("/api/v2/moderation/flag", FlagResponse, json=json) + @telemetry.operation_name("getstream.api.moderation.query_moderation_flags") def query_moderation_flags( self, limit: Optional[int] = None, @@ -274,11 +278,11 @@ def query_moderation_flags( json = build_body_dict( limit=limit, next=next, prev=prev, sort=sort, filter=filter ) - return self.post( "/api/v2/moderation/flags", QueryModerationFlagsResponse, json=json ) + @telemetry.operation_name("getstream.api.moderation.query_moderation_logs") def query_moderation_logs( self, limit: Optional[int] = None, @@ -298,11 +302,11 @@ def query_moderation_logs( filter=filter, user=user, ) - return self.post( "/api/v2/moderation/logs", QueryModerationLogsResponse, json=json ) + @telemetry.operation_name("getstream.api.moderation.upsert_moderation_rule") def upsert_moderation_rule( self, name: str, @@ -330,23 +334,25 @@ def upsert_moderation_rule( config_keys=config_keys, groups=groups, ) - return self.post( "/api/v2/moderation/moderation_rule", UpsertModerationRuleResponse, json=json, ) + @telemetry.operation_name("getstream.api.moderation.delete_moderation_rule") def delete_moderation_rule(self) -> StreamResponse[DeleteModerationRuleResponse]: return self.delete( "/api/v2/moderation/moderation_rule/{id}", DeleteModerationRuleResponse ) + @telemetry.operation_name("getstream.api.moderation.get_moderation_rule") def get_moderation_rule(self) -> StreamResponse[GetModerationRuleResponse]: return self.get( "/api/v2/moderation/moderation_rule/{id}", GetModerationRuleResponse ) + @telemetry.operation_name("getstream.api.moderation.query_moderation_rules") def query_moderation_rules( self, limit: Optional[int] = None, @@ -366,13 +372,13 @@ def query_moderation_rules( filter=filter, user=user, ) - return self.post( "/api/v2/moderation/moderation_rules", QueryModerationRulesResponse, json=json, ) + @telemetry.operation_name("getstream.api.moderation.mute") def mute( self, target_ids: List[str], @@ -383,9 +389,9 @@ def mute( json = build_body_dict( target_ids=target_ids, timeout=timeout, user_id=user_id, user=user ) - return self.post("/api/v2/moderation/mute", MuteResponse, json=json) + @telemetry.operation_name("getstream.api.moderation.query_review_queue") def query_review_queue( self, limit: Optional[int] = None, @@ -413,24 +419,24 @@ def query_review_queue( filter=filter, user=user, ) - return self.post( "/api/v2/moderation/review_queue", QueryReviewQueueResponse, json=json ) + @telemetry.operation_name("getstream.api.moderation.get_review_queue_item") def get_review_queue_item( self, id: str ) -> StreamResponse[GetReviewQueueItemResponse]: path_params = { "id": id, } - return self.get( "/api/v2/moderation/review_queue/{id}", GetReviewQueueItemResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.moderation.submit_action") def submit_action( self, action_type: str, @@ -460,11 +466,11 @@ def submit_action( unban=unban, user=user, ) - return self.post( "/api/v2/moderation/submit_action", SubmitActionResponse, json=json ) + @telemetry.operation_name("getstream.api.moderation.unban") def unban( self, target_user_id: str, @@ -479,7 +485,6 @@ def unban( created_by=created_by, ) json = build_body_dict(unbanned_by_id=unbanned_by_id, unbanned_by=unbanned_by) - return self.post( "/api/v2/moderation/unban", UnbanResponse, @@ -487,6 +492,7 @@ def unban( json=json, ) + @telemetry.operation_name("getstream.api.moderation.unmute") def unmute( self, target_ids: List[str], @@ -494,5 +500,4 @@ def unmute( user: Optional[UserRequest] = None, ) -> StreamResponse[UnmuteResponse]: json = build_body_dict(target_ids=target_ids, user_id=user_id, user=user) - return self.post("/api/v2/moderation/unmute", UnmuteResponse, json=json) diff --git a/getstream/stream.py b/getstream/stream.py index 4e2ca1c7..96a8de64 100644 --- a/getstream/stream.py +++ b/getstream/stream.py @@ -194,7 +194,7 @@ def moderation(self) -> AsyncModerationClient: def feeds(self): raise NotImplementedError("Feeds not supported for async client") - @telemetry.operation_name("create_user") + @telemetry.operation_name("getstream.api.common.create_user") async def create_user(self, name: str = "", id: str = str(uuid4()), image=""): """ Creates or updates users. This method performs an "upsert" operation, @@ -207,7 +207,7 @@ async def create_user(self, name: str = "", id: str = str(uuid4()), image=""): user = response.data.users[user.id] return user - @telemetry.operation_name("upsert_users") + @telemetry.operation_name("getstream.api.common.upsert_users") async def upsert_users(self, *users: UserRequest): """ Creates or updates users. This method performs an "upsert" operation, @@ -305,7 +305,7 @@ def feeds(self) -> FeedsClient: stream=self, ) - @telemetry.operation_name("create_user") + @telemetry.operation_name("getstream.api.common.create_user") def create_user(self, name: str = "", id: str = str(uuid4()), image=""): """ Creates or updates users. This method performs an "upsert" operation, @@ -318,7 +318,7 @@ def create_user(self, name: str = "", id: str = str(uuid4()), image=""): user = response.data.users[user.id] return user - @telemetry.operation_name("upsert_users") + @telemetry.operation_name("getstream.api.common.upsert_users") def upsert_users(self, *users: UserRequest): """ Creates or updates users. This method performs an "upsert" operation, diff --git a/getstream/video/async_call.py b/getstream/video/async_call.py index 62f8c010..b9eb581f 100644 --- a/getstream/video/async_call.py +++ b/getstream/video/async_call.py @@ -1,8 +1,8 @@ # Code generated by GetStream internal OpenAPI code generator. DO NOT EDIT. +from getstream.common.telemetry import attach_call_cid_async from getstream.models import * from getstream.stream_response import StreamResponse -from getstream.video import BaseCall -from getstream.common.telemetry import attach_call_cid_async +from video import BaseCall class Call(BaseCall): diff --git a/getstream/video/async_rest_client.py b/getstream/video/async_rest_client.py index 6db6afad..be3633ac 100644 --- a/getstream/video/async_rest_client.py +++ b/getstream/video/async_rest_client.py @@ -1,5 +1,6 @@ # Code generated by GetStream internal OpenAPI code generator. DO NOT EDIT. from getstream.base import AsyncBaseClient +from getstream.common import telemetry from getstream.models import * from getstream.stream_response import StreamResponse from getstream.utils import build_query_param, build_body_dict @@ -21,6 +22,7 @@ def __init__(self, api_key: str, base_url: str, timeout: float, token: str): token=token, ) + @telemetry.operation_name("getstream.api.video.get_active_calls_status") async def get_active_calls_status( self, ) -> StreamResponse[GetActiveCallsStatusResponse]: @@ -28,6 +30,7 @@ async def get_active_calls_status( "/api/v2/video/active_calls_status", GetActiveCallsStatusResponse ) + @telemetry.operation_name("getstream.api.video.query_user_feedback") async def query_user_feedback( self, full: Optional[bool] = None, @@ -45,7 +48,6 @@ async def query_user_feedback( sort=sort, filter_conditions=filter_conditions, ) - return await self.post( "/api/v2/video/call/feedback", QueryUserFeedbackResponse, @@ -53,6 +55,7 @@ async def query_user_feedback( json=json, ) + @telemetry.operation_name("getstream.api.video.query_call_members") async def query_call_members( self, id: str, @@ -72,11 +75,11 @@ async def query_call_members( sort=sort, filter_conditions=filter_conditions, ) - return await self.post( "/api/v2/video/call/members", QueryCallMembersResponse, json=json ) + @telemetry.operation_name("getstream.api.video.query_call_stats") async def query_call_stats( self, limit: Optional[int] = None, @@ -92,11 +95,11 @@ async def query_call_stats( sort=sort, filter_conditions=filter_conditions, ) - return await self.post( "/api/v2/video/call/stats", QueryCallStatsResponse, json=json ) + @telemetry.operation_name("getstream.api.video.get_call") async def get_call( self, type: str, @@ -113,7 +116,6 @@ async def get_call( "type": type, "id": id, } - return await self.get( "/api/v2/video/call/{type}/{id}", GetCallResponse, @@ -121,6 +123,7 @@ async def get_call( path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.update_call") async def update_call( self, type: str, @@ -136,7 +139,6 @@ async def update_call( json = build_body_dict( starts_at=starts_at, custom=custom, settings_override=settings_override ) - return await self.patch( "/api/v2/video/call/{type}/{id}", UpdateCallResponse, @@ -144,6 +146,7 @@ async def update_call( json=json, ) + @telemetry.operation_name("getstream.api.video.get_or_create_call") async def get_or_create_call( self, type: str, @@ -165,7 +168,6 @@ async def get_or_create_call( video=video, data=data, ) - return await self.post( "/api/v2/video/call/{type}/{id}", GetOrCreateCallResponse, @@ -173,6 +175,7 @@ async def get_or_create_call( json=json, ) + @telemetry.operation_name("getstream.api.video.block_user") async def block_user( self, type: str, id: str, user_id: str ) -> StreamResponse[BlockUserResponse]: @@ -181,7 +184,6 @@ async def block_user( "id": id, } json = build_body_dict(user_id=user_id) - return await self.post( "/api/v2/video/call/{type}/{id}/block", BlockUserResponse, @@ -189,6 +191,7 @@ async def block_user( json=json, ) + @telemetry.operation_name("getstream.api.video.send_closed_caption") async def send_closed_caption( self, type: str, @@ -218,7 +221,6 @@ async def send_closed_caption( user_id=user_id, user=user, ) - return await self.post( "/api/v2/video/call/{type}/{id}/closed_captions", SendClosedCaptionResponse, @@ -226,6 +228,7 @@ async def send_closed_caption( json=json, ) + @telemetry.operation_name("getstream.api.video.delete_call") async def delete_call( self, type: str, id: str, hard: Optional[bool] = None ) -> StreamResponse[DeleteCallResponse]: @@ -234,7 +237,6 @@ async def delete_call( "id": id, } json = build_body_dict(hard=hard) - return await self.post( "/api/v2/video/call/{type}/{id}/delete", DeleteCallResponse, @@ -242,6 +244,7 @@ async def delete_call( json=json, ) + @telemetry.operation_name("getstream.api.video.send_call_event") async def send_call_event( self, type: str, @@ -255,7 +258,6 @@ async def send_call_event( "id": id, } json = build_body_dict(user_id=user_id, custom=custom, user=user) - return await self.post( "/api/v2/video/call/{type}/{id}/event", SendCallEventResponse, @@ -263,6 +265,7 @@ async def send_call_event( json=json, ) + @telemetry.operation_name("getstream.api.video.collect_user_feedback") async def collect_user_feedback( self, type: str, @@ -286,7 +289,6 @@ async def collect_user_feedback( user_session_id=user_session_id, custom=custom, ) - return await self.post( "/api/v2/video/call/{type}/{id}/feedback", CollectUserFeedbackResponse, @@ -294,6 +296,7 @@ async def collect_user_feedback( json=json, ) + @telemetry.operation_name("getstream.api.video.go_live") async def go_live( self, type: str, @@ -317,7 +320,6 @@ async def go_live( start_transcription=start_transcription, transcription_storage_name=transcription_storage_name, ) - return await self.post( "/api/v2/video/call/{type}/{id}/go_live", GoLiveResponse, @@ -325,6 +327,7 @@ async def go_live( json=json, ) + @telemetry.operation_name("getstream.api.video.kick_user") async def kick_user( self, type: str, @@ -341,7 +344,6 @@ async def kick_user( json = build_body_dict( user_id=user_id, block=block, kicked_by_id=kicked_by_id, kicked_by=kicked_by ) - return await self.post( "/api/v2/video/call/{type}/{id}/kick", KickUserResponse, @@ -349,18 +351,19 @@ async def kick_user( json=json, ) + @telemetry.operation_name("getstream.api.video.end_call") async def end_call(self, type: str, id: str) -> StreamResponse[EndCallResponse]: path_params = { "type": type, "id": id, } - return await self.post( "/api/v2/video/call/{type}/{id}/mark_ended", EndCallResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.update_call_members") async def update_call_members( self, type: str, @@ -375,7 +378,6 @@ async def update_call_members( json = build_body_dict( remove_members=remove_members, update_members=update_members ) - return await self.post( "/api/v2/video/call/{type}/{id}/members", UpdateCallMembersResponse, @@ -383,6 +385,7 @@ async def update_call_members( json=json, ) + @telemetry.operation_name("getstream.api.video.mute_users") async def mute_users( self, type: str, @@ -410,7 +413,6 @@ async def mute_users( user_ids=user_ids, muted_by=muted_by, ) - return await self.post( "/api/v2/video/call/{type}/{id}/mute_users", MuteUsersResponse, @@ -418,6 +420,7 @@ async def mute_users( json=json, ) + @telemetry.operation_name("getstream.api.video.query_call_participants") async def query_call_participants( self, id: str, @@ -431,7 +434,6 @@ async def query_call_participants( "type": type, } json = build_body_dict(filter_conditions=filter_conditions) - return await self.post( "/api/v2/video/call/{type}/{id}/participants", QueryCallParticipantsResponse, @@ -440,6 +442,7 @@ async def query_call_participants( json=json, ) + @telemetry.operation_name("getstream.api.video.video_pin") async def video_pin( self, type: str, id: str, session_id: str, user_id: str ) -> StreamResponse[PinResponse]: @@ -448,7 +451,6 @@ async def video_pin( "id": id, } json = build_body_dict(session_id=session_id, user_id=user_id) - return await self.post( "/api/v2/video/call/{type}/{id}/pin", PinResponse, @@ -456,6 +458,7 @@ async def video_pin( json=json, ) + @telemetry.operation_name("getstream.api.video.list_recordings") async def list_recordings( self, type: str, id: str ) -> StreamResponse[ListRecordingsResponse]: @@ -463,13 +466,13 @@ async def list_recordings( "type": type, "id": id, } - return await self.get( "/api/v2/video/call/{type}/{id}/recordings", ListRecordingsResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.get_call_report") async def get_call_report( self, type: str, id: str, session_id: Optional[str] = None ) -> StreamResponse[GetCallReportResponse]: @@ -478,7 +481,6 @@ async def get_call_report( "type": type, "id": id, } - return await self.get( "/api/v2/video/call/{type}/{id}/report", GetCallReportResponse, @@ -486,6 +488,7 @@ async def get_call_report( path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.start_rtmp_broadcasts") async def start_rtmp_broadcasts( self, type: str, id: str, broadcasts: List[RTMPBroadcastRequest] ) -> StreamResponse[StartRTMPBroadcastsResponse]: @@ -494,7 +497,6 @@ async def start_rtmp_broadcasts( "id": id, } json = build_body_dict(broadcasts=broadcasts) - return await self.post( "/api/v2/video/call/{type}/{id}/rtmp_broadcasts", StartRTMPBroadcastsResponse, @@ -502,6 +504,7 @@ async def start_rtmp_broadcasts( json=json, ) + @telemetry.operation_name("getstream.api.video.stop_all_rtmp_broadcasts") async def stop_all_rtmp_broadcasts( self, type: str, id: str ) -> StreamResponse[StopAllRTMPBroadcastsResponse]: @@ -509,13 +512,13 @@ async def stop_all_rtmp_broadcasts( "type": type, "id": id, } - return await self.post( "/api/v2/video/call/{type}/{id}/rtmp_broadcasts/stop", StopAllRTMPBroadcastsResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.stop_rtmp_broadcast") async def stop_rtmp_broadcast( self, type: str, @@ -528,7 +531,6 @@ async def stop_rtmp_broadcast( "name": name, } json = build_body_dict() - return await self.post( "/api/v2/video/call/{type}/{id}/rtmp_broadcasts/{name}/stop", StopRTMPBroadcastsResponse, @@ -536,6 +538,7 @@ async def stop_rtmp_broadcast( json=json, ) + @telemetry.operation_name("getstream.api.video.start_hls_broadcasting") async def start_hls_broadcasting( self, type: str, id: str ) -> StreamResponse[StartHLSBroadcastingResponse]: @@ -543,13 +546,13 @@ async def start_hls_broadcasting( "type": type, "id": id, } - return await self.post( "/api/v2/video/call/{type}/{id}/start_broadcasting", StartHLSBroadcastingResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.start_closed_captions") async def start_closed_captions( self, type: str, @@ -569,7 +572,6 @@ async def start_closed_captions( language=language, speech_segment_config=speech_segment_config, ) - return await self.post( "/api/v2/video/call/{type}/{id}/start_closed_captions", StartClosedCaptionsResponse, @@ -577,6 +579,7 @@ async def start_closed_captions( json=json, ) + @telemetry.operation_name("getstream.api.video.start_frame_recording") async def start_frame_recording( self, type: str, id: str, recording_external_storage: Optional[str] = None ) -> StreamResponse[StartFrameRecordingResponse]: @@ -585,7 +588,6 @@ async def start_frame_recording( "id": id, } json = build_body_dict(recording_external_storage=recording_external_storage) - return await self.post( "/api/v2/video/call/{type}/{id}/start_frame_recording", StartFrameRecordingResponse, @@ -593,6 +595,7 @@ async def start_frame_recording( json=json, ) + @telemetry.operation_name("getstream.api.video.start_recording") async def start_recording( self, type: str, id: str, recording_external_storage: Optional[str] = None ) -> StreamResponse[StartRecordingResponse]: @@ -601,7 +604,6 @@ async def start_recording( "id": id, } json = build_body_dict(recording_external_storage=recording_external_storage) - return await self.post( "/api/v2/video/call/{type}/{id}/start_recording", StartRecordingResponse, @@ -609,6 +611,7 @@ async def start_recording( json=json, ) + @telemetry.operation_name("getstream.api.video.start_transcription") async def start_transcription( self, type: str, @@ -626,7 +629,6 @@ async def start_transcription( language=language, transcription_external_storage=transcription_external_storage, ) - return await self.post( "/api/v2/video/call/{type}/{id}/start_transcription", StartTranscriptionResponse, @@ -634,6 +636,7 @@ async def start_transcription( json=json, ) + @telemetry.operation_name("getstream.api.video.stop_hls_broadcasting") async def stop_hls_broadcasting( self, type: str, id: str ) -> StreamResponse[StopHLSBroadcastingResponse]: @@ -641,13 +644,13 @@ async def stop_hls_broadcasting( "type": type, "id": id, } - return await self.post( "/api/v2/video/call/{type}/{id}/stop_broadcasting", StopHLSBroadcastingResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.stop_closed_captions") async def stop_closed_captions( self, type: str, id: str, stop_transcription: Optional[bool] = None ) -> StreamResponse[StopClosedCaptionsResponse]: @@ -656,7 +659,6 @@ async def stop_closed_captions( "id": id, } json = build_body_dict(stop_transcription=stop_transcription) - return await self.post( "/api/v2/video/call/{type}/{id}/stop_closed_captions", StopClosedCaptionsResponse, @@ -664,6 +666,7 @@ async def stop_closed_captions( json=json, ) + @telemetry.operation_name("getstream.api.video.stop_frame_recording") async def stop_frame_recording( self, type: str, id: str ) -> StreamResponse[StopFrameRecordingResponse]: @@ -671,13 +674,13 @@ async def stop_frame_recording( "type": type, "id": id, } - return await self.post( "/api/v2/video/call/{type}/{id}/stop_frame_recording", StopFrameRecordingResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.stop_live") async def stop_live( self, type: str, @@ -699,7 +702,6 @@ async def stop_live( continue_rtmp_broadcasts=continue_rtmp_broadcasts, continue_transcription=continue_transcription, ) - return await self.post( "/api/v2/video/call/{type}/{id}/stop_live", StopLiveResponse, @@ -707,6 +709,7 @@ async def stop_live( json=json, ) + @telemetry.operation_name("getstream.api.video.stop_recording") async def stop_recording( self, type: str, id: str ) -> StreamResponse[StopRecordingResponse]: @@ -714,13 +717,13 @@ async def stop_recording( "type": type, "id": id, } - return await self.post( "/api/v2/video/call/{type}/{id}/stop_recording", StopRecordingResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.stop_transcription") async def stop_transcription( self, type: str, id: str, stop_closed_captions: Optional[bool] = None ) -> StreamResponse[StopTranscriptionResponse]: @@ -729,7 +732,6 @@ async def stop_transcription( "id": id, } json = build_body_dict(stop_closed_captions=stop_closed_captions) - return await self.post( "/api/v2/video/call/{type}/{id}/stop_transcription", StopTranscriptionResponse, @@ -737,6 +739,7 @@ async def stop_transcription( json=json, ) + @telemetry.operation_name("getstream.api.video.list_transcriptions") async def list_transcriptions( self, type: str, id: str ) -> StreamResponse[ListTranscriptionsResponse]: @@ -744,13 +747,13 @@ async def list_transcriptions( "type": type, "id": id, } - return await self.get( "/api/v2/video/call/{type}/{id}/transcriptions", ListTranscriptionsResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.unblock_user") async def unblock_user( self, type: str, id: str, user_id: str ) -> StreamResponse[UnblockUserResponse]: @@ -759,7 +762,6 @@ async def unblock_user( "id": id, } json = build_body_dict(user_id=user_id) - return await self.post( "/api/v2/video/call/{type}/{id}/unblock", UnblockUserResponse, @@ -767,6 +769,7 @@ async def unblock_user( json=json, ) + @telemetry.operation_name("getstream.api.video.video_unpin") async def video_unpin( self, type: str, id: str, session_id: str, user_id: str ) -> StreamResponse[UnpinResponse]: @@ -775,7 +778,6 @@ async def video_unpin( "id": id, } json = build_body_dict(session_id=session_id, user_id=user_id) - return await self.post( "/api/v2/video/call/{type}/{id}/unpin", UnpinResponse, @@ -783,6 +785,7 @@ async def video_unpin( json=json, ) + @telemetry.operation_name("getstream.api.video.update_user_permissions") async def update_user_permissions( self, type: str, @@ -800,7 +803,6 @@ async def update_user_permissions( grant_permissions=grant_permissions, revoke_permissions=revoke_permissions, ) - return await self.post( "/api/v2/video/call/{type}/{id}/user_permissions", UpdateUserPermissionsResponse, @@ -808,6 +810,7 @@ async def update_user_permissions( json=json, ) + @telemetry.operation_name("getstream.api.video.delete_recording") async def delete_recording( self, type: str, id: str, session: str, filename: str ) -> StreamResponse[DeleteRecordingResponse]: @@ -817,13 +820,13 @@ async def delete_recording( "session": session, "filename": filename, } - return await self.delete( "/api/v2/video/call/{type}/{id}/{session}/recordings/{filename}", DeleteRecordingResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.delete_transcription") async def delete_transcription( self, type: str, id: str, session: str, filename: str ) -> StreamResponse[DeleteTranscriptionResponse]: @@ -833,13 +836,37 @@ async def delete_transcription( "session": session, "filename": filename, } - return await self.delete( "/api/v2/video/call/{type}/{id}/{session}/transcriptions/{filename}", DeleteTranscriptionResponse, path_params=path_params, ) + @telemetry.operation_name( + "getstream.api.video.query_call_session_participant_stats" + ) + async def query_call_session_participant_stats( + self, + call_type: str, + call_id: str, + session: str, + sort: Optional[List[SortParamRequest]] = None, + filter_conditions: Optional[Dict[str, object]] = None, + ) -> StreamResponse[QueryCallSessionParticipantStatsResponse]: + query_params = build_query_param(sort=sort, filter_conditions=filter_conditions) + path_params = { + "call_type": call_type, + "call_id": call_id, + "session": session, + } + return await self.get( + "/api/v2/video/call_stats/{call_type}/{call_id}/{session}/participants", + QueryCallSessionParticipantStatsResponse, + query_params=query_params, + path_params=path_params, + ) + + @telemetry.operation_name("getstream.api.video.query_calls") async def query_calls( self, limit: Optional[int] = None, @@ -855,12 +882,13 @@ async def query_calls( sort=sort, filter_conditions=filter_conditions, ) - return await self.post("/api/v2/video/calls", QueryCallsResponse, json=json) + @telemetry.operation_name("getstream.api.video.list_call_types") async def list_call_types(self) -> StreamResponse[ListCallTypeResponse]: return await self.get("/api/v2/video/calltypes", ListCallTypeResponse) + @telemetry.operation_name("getstream.api.video.create_call_type") async def create_call_type( self, name: str, @@ -876,31 +904,31 @@ async def create_call_type( notification_settings=notification_settings, settings=settings, ) - return await self.post( "/api/v2/video/calltypes", CreateCallTypeResponse, json=json ) + @telemetry.operation_name("getstream.api.video.delete_call_type") async def delete_call_type(self, name: str) -> StreamResponse[Response]: path_params = { "name": name, } - return await self.delete( "/api/v2/video/calltypes/{name}", Response, path_params=path_params ) + @telemetry.operation_name("getstream.api.video.get_call_type") async def get_call_type(self, name: str) -> StreamResponse[GetCallTypeResponse]: path_params = { "name": name, } - return await self.get( "/api/v2/video/calltypes/{name}", GetCallTypeResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.update_call_type") async def update_call_type( self, name: str, @@ -918,7 +946,6 @@ async def update_call_type( notification_settings=notification_settings, settings=settings, ) - return await self.put( "/api/v2/video/calltypes/{name}", UpdateCallTypeResponse, @@ -926,9 +953,11 @@ async def update_call_type( json=json, ) + @telemetry.operation_name("getstream.api.video.get_edges") async def get_edges(self) -> StreamResponse[GetEdgesResponse]: return await self.get("/api/v2/video/edges", GetEdgesResponse) + @telemetry.operation_name("getstream.api.video.query_aggregate_call_stats") async def query_aggregate_call_stats( self, _from: Optional[str] = None, @@ -936,7 +965,6 @@ async def query_aggregate_call_stats( report_types: Optional[List[str]] = None, ) -> StreamResponse[QueryAggregateCallStatsResponse]: json = build_body_dict(_from=_from, to=to, report_types=report_types) - return await self.post( "/api/v2/video/stats", QueryAggregateCallStatsResponse, json=json ) diff --git a/getstream/video/call.py b/getstream/video/call.py index ccddf6bb..45829600 100644 --- a/getstream/video/call.py +++ b/getstream/video/call.py @@ -1,8 +1,8 @@ # Code generated by GetStream internal OpenAPI code generator. DO NOT EDIT. +from getstream.common.telemetry import attach_call_cid from getstream.models import * from getstream.stream_response import StreamResponse -from getstream.video import BaseCall -from getstream.common.telemetry import attach_call_cid +from video import BaseCall class Call(BaseCall): diff --git a/getstream/video/rest_client.py b/getstream/video/rest_client.py index 9c3996dd..f1c86dbf 100644 --- a/getstream/video/rest_client.py +++ b/getstream/video/rest_client.py @@ -1,5 +1,6 @@ # Code generated by GetStream internal OpenAPI code generator. DO NOT EDIT. from getstream.base import BaseClient +from getstream.common import telemetry from getstream.models import * from getstream.stream_response import StreamResponse from getstream.utils import build_query_param, build_body_dict @@ -21,11 +22,13 @@ def __init__(self, api_key: str, base_url: str, timeout: float, token: str): token=token, ) + @telemetry.operation_name("getstream.api.video.get_active_calls_status") def get_active_calls_status(self) -> StreamResponse[GetActiveCallsStatusResponse]: return self.get( "/api/v2/video/active_calls_status", GetActiveCallsStatusResponse ) + @telemetry.operation_name("getstream.api.video.query_user_feedback") def query_user_feedback( self, full: Optional[bool] = None, @@ -43,7 +46,6 @@ def query_user_feedback( sort=sort, filter_conditions=filter_conditions, ) - return self.post( "/api/v2/video/call/feedback", QueryUserFeedbackResponse, @@ -51,6 +53,7 @@ def query_user_feedback( json=json, ) + @telemetry.operation_name("getstream.api.video.query_call_members") def query_call_members( self, id: str, @@ -70,11 +73,11 @@ def query_call_members( sort=sort, filter_conditions=filter_conditions, ) - return self.post( "/api/v2/video/call/members", QueryCallMembersResponse, json=json ) + @telemetry.operation_name("getstream.api.video.query_call_stats") def query_call_stats( self, limit: Optional[int] = None, @@ -90,9 +93,9 @@ def query_call_stats( sort=sort, filter_conditions=filter_conditions, ) - return self.post("/api/v2/video/call/stats", QueryCallStatsResponse, json=json) + @telemetry.operation_name("getstream.api.video.get_call") def get_call( self, type: str, @@ -109,7 +112,6 @@ def get_call( "type": type, "id": id, } - return self.get( "/api/v2/video/call/{type}/{id}", GetCallResponse, @@ -117,6 +119,7 @@ def get_call( path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.update_call") def update_call( self, type: str, @@ -132,7 +135,6 @@ def update_call( json = build_body_dict( starts_at=starts_at, custom=custom, settings_override=settings_override ) - return self.patch( "/api/v2/video/call/{type}/{id}", UpdateCallResponse, @@ -140,6 +142,7 @@ def update_call( json=json, ) + @telemetry.operation_name("getstream.api.video.get_or_create_call") def get_or_create_call( self, type: str, @@ -161,7 +164,6 @@ def get_or_create_call( video=video, data=data, ) - return self.post( "/api/v2/video/call/{type}/{id}", GetOrCreateCallResponse, @@ -169,6 +171,7 @@ def get_or_create_call( json=json, ) + @telemetry.operation_name("getstream.api.video.block_user") def block_user( self, type: str, id: str, user_id: str ) -> StreamResponse[BlockUserResponse]: @@ -177,7 +180,6 @@ def block_user( "id": id, } json = build_body_dict(user_id=user_id) - return self.post( "/api/v2/video/call/{type}/{id}/block", BlockUserResponse, @@ -185,6 +187,7 @@ def block_user( json=json, ) + @telemetry.operation_name("getstream.api.video.send_closed_caption") def send_closed_caption( self, type: str, @@ -214,7 +217,6 @@ def send_closed_caption( user_id=user_id, user=user, ) - return self.post( "/api/v2/video/call/{type}/{id}/closed_captions", SendClosedCaptionResponse, @@ -222,6 +224,7 @@ def send_closed_caption( json=json, ) + @telemetry.operation_name("getstream.api.video.delete_call") def delete_call( self, type: str, id: str, hard: Optional[bool] = None ) -> StreamResponse[DeleteCallResponse]: @@ -230,7 +233,6 @@ def delete_call( "id": id, } json = build_body_dict(hard=hard) - return self.post( "/api/v2/video/call/{type}/{id}/delete", DeleteCallResponse, @@ -238,6 +240,7 @@ def delete_call( json=json, ) + @telemetry.operation_name("getstream.api.video.send_call_event") def send_call_event( self, type: str, @@ -251,7 +254,6 @@ def send_call_event( "id": id, } json = build_body_dict(user_id=user_id, custom=custom, user=user) - return self.post( "/api/v2/video/call/{type}/{id}/event", SendCallEventResponse, @@ -259,6 +261,7 @@ def send_call_event( json=json, ) + @telemetry.operation_name("getstream.api.video.collect_user_feedback") def collect_user_feedback( self, type: str, @@ -282,7 +285,6 @@ def collect_user_feedback( user_session_id=user_session_id, custom=custom, ) - return self.post( "/api/v2/video/call/{type}/{id}/feedback", CollectUserFeedbackResponse, @@ -290,6 +292,7 @@ def collect_user_feedback( json=json, ) + @telemetry.operation_name("getstream.api.video.go_live") def go_live( self, type: str, @@ -313,7 +316,6 @@ def go_live( start_transcription=start_transcription, transcription_storage_name=transcription_storage_name, ) - return self.post( "/api/v2/video/call/{type}/{id}/go_live", GoLiveResponse, @@ -321,6 +323,7 @@ def go_live( json=json, ) + @telemetry.operation_name("getstream.api.video.kick_user") def kick_user( self, type: str, @@ -337,7 +340,6 @@ def kick_user( json = build_body_dict( user_id=user_id, block=block, kicked_by_id=kicked_by_id, kicked_by=kicked_by ) - return self.post( "/api/v2/video/call/{type}/{id}/kick", KickUserResponse, @@ -345,18 +347,19 @@ def kick_user( json=json, ) + @telemetry.operation_name("getstream.api.video.end_call") def end_call(self, type: str, id: str) -> StreamResponse[EndCallResponse]: path_params = { "type": type, "id": id, } - return self.post( "/api/v2/video/call/{type}/{id}/mark_ended", EndCallResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.update_call_members") def update_call_members( self, type: str, @@ -371,7 +374,6 @@ def update_call_members( json = build_body_dict( remove_members=remove_members, update_members=update_members ) - return self.post( "/api/v2/video/call/{type}/{id}/members", UpdateCallMembersResponse, @@ -379,6 +381,7 @@ def update_call_members( json=json, ) + @telemetry.operation_name("getstream.api.video.mute_users") def mute_users( self, type: str, @@ -406,7 +409,6 @@ def mute_users( user_ids=user_ids, muted_by=muted_by, ) - return self.post( "/api/v2/video/call/{type}/{id}/mute_users", MuteUsersResponse, @@ -414,6 +416,7 @@ def mute_users( json=json, ) + @telemetry.operation_name("getstream.api.video.query_call_participants") def query_call_participants( self, id: str, @@ -427,7 +430,6 @@ def query_call_participants( "type": type, } json = build_body_dict(filter_conditions=filter_conditions) - return self.post( "/api/v2/video/call/{type}/{id}/participants", QueryCallParticipantsResponse, @@ -436,6 +438,7 @@ def query_call_participants( json=json, ) + @telemetry.operation_name("getstream.api.video.video_pin") def video_pin( self, type: str, id: str, session_id: str, user_id: str ) -> StreamResponse[PinResponse]: @@ -444,7 +447,6 @@ def video_pin( "id": id, } json = build_body_dict(session_id=session_id, user_id=user_id) - return self.post( "/api/v2/video/call/{type}/{id}/pin", PinResponse, @@ -452,6 +454,7 @@ def video_pin( json=json, ) + @telemetry.operation_name("getstream.api.video.list_recordings") def list_recordings( self, type: str, id: str ) -> StreamResponse[ListRecordingsResponse]: @@ -459,13 +462,13 @@ def list_recordings( "type": type, "id": id, } - return self.get( "/api/v2/video/call/{type}/{id}/recordings", ListRecordingsResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.get_call_report") def get_call_report( self, type: str, id: str, session_id: Optional[str] = None ) -> StreamResponse[GetCallReportResponse]: @@ -474,7 +477,6 @@ def get_call_report( "type": type, "id": id, } - return self.get( "/api/v2/video/call/{type}/{id}/report", GetCallReportResponse, @@ -482,6 +484,7 @@ def get_call_report( path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.start_rtmp_broadcasts") def start_rtmp_broadcasts( self, type: str, id: str, broadcasts: List[RTMPBroadcastRequest] ) -> StreamResponse[StartRTMPBroadcastsResponse]: @@ -490,7 +493,6 @@ def start_rtmp_broadcasts( "id": id, } json = build_body_dict(broadcasts=broadcasts) - return self.post( "/api/v2/video/call/{type}/{id}/rtmp_broadcasts", StartRTMPBroadcastsResponse, @@ -498,6 +500,7 @@ def start_rtmp_broadcasts( json=json, ) + @telemetry.operation_name("getstream.api.video.stop_all_rtmp_broadcasts") def stop_all_rtmp_broadcasts( self, type: str, id: str ) -> StreamResponse[StopAllRTMPBroadcastsResponse]: @@ -505,13 +508,13 @@ def stop_all_rtmp_broadcasts( "type": type, "id": id, } - return self.post( "/api/v2/video/call/{type}/{id}/rtmp_broadcasts/stop", StopAllRTMPBroadcastsResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.stop_rtmp_broadcast") def stop_rtmp_broadcast( self, type: str, @@ -524,7 +527,6 @@ def stop_rtmp_broadcast( "name": name, } json = build_body_dict() - return self.post( "/api/v2/video/call/{type}/{id}/rtmp_broadcasts/{name}/stop", StopRTMPBroadcastsResponse, @@ -532,6 +534,7 @@ def stop_rtmp_broadcast( json=json, ) + @telemetry.operation_name("getstream.api.video.start_hls_broadcasting") def start_hls_broadcasting( self, type: str, id: str ) -> StreamResponse[StartHLSBroadcastingResponse]: @@ -539,13 +542,13 @@ def start_hls_broadcasting( "type": type, "id": id, } - return self.post( "/api/v2/video/call/{type}/{id}/start_broadcasting", StartHLSBroadcastingResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.start_closed_captions") def start_closed_captions( self, type: str, @@ -565,7 +568,6 @@ def start_closed_captions( language=language, speech_segment_config=speech_segment_config, ) - return self.post( "/api/v2/video/call/{type}/{id}/start_closed_captions", StartClosedCaptionsResponse, @@ -573,6 +575,7 @@ def start_closed_captions( json=json, ) + @telemetry.operation_name("getstream.api.video.start_frame_recording") def start_frame_recording( self, type: str, id: str, recording_external_storage: Optional[str] = None ) -> StreamResponse[StartFrameRecordingResponse]: @@ -581,7 +584,6 @@ def start_frame_recording( "id": id, } json = build_body_dict(recording_external_storage=recording_external_storage) - return self.post( "/api/v2/video/call/{type}/{id}/start_frame_recording", StartFrameRecordingResponse, @@ -589,6 +591,7 @@ def start_frame_recording( json=json, ) + @telemetry.operation_name("getstream.api.video.start_recording") def start_recording( self, type: str, id: str, recording_external_storage: Optional[str] = None ) -> StreamResponse[StartRecordingResponse]: @@ -597,7 +600,6 @@ def start_recording( "id": id, } json = build_body_dict(recording_external_storage=recording_external_storage) - return self.post( "/api/v2/video/call/{type}/{id}/start_recording", StartRecordingResponse, @@ -605,6 +607,7 @@ def start_recording( json=json, ) + @telemetry.operation_name("getstream.api.video.start_transcription") def start_transcription( self, type: str, @@ -622,7 +625,6 @@ def start_transcription( language=language, transcription_external_storage=transcription_external_storage, ) - return self.post( "/api/v2/video/call/{type}/{id}/start_transcription", StartTranscriptionResponse, @@ -630,6 +632,7 @@ def start_transcription( json=json, ) + @telemetry.operation_name("getstream.api.video.stop_hls_broadcasting") def stop_hls_broadcasting( self, type: str, id: str ) -> StreamResponse[StopHLSBroadcastingResponse]: @@ -637,13 +640,13 @@ def stop_hls_broadcasting( "type": type, "id": id, } - return self.post( "/api/v2/video/call/{type}/{id}/stop_broadcasting", StopHLSBroadcastingResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.stop_closed_captions") def stop_closed_captions( self, type: str, id: str, stop_transcription: Optional[bool] = None ) -> StreamResponse[StopClosedCaptionsResponse]: @@ -652,7 +655,6 @@ def stop_closed_captions( "id": id, } json = build_body_dict(stop_transcription=stop_transcription) - return self.post( "/api/v2/video/call/{type}/{id}/stop_closed_captions", StopClosedCaptionsResponse, @@ -660,6 +662,7 @@ def stop_closed_captions( json=json, ) + @telemetry.operation_name("getstream.api.video.stop_frame_recording") def stop_frame_recording( self, type: str, id: str ) -> StreamResponse[StopFrameRecordingResponse]: @@ -667,13 +670,13 @@ def stop_frame_recording( "type": type, "id": id, } - return self.post( "/api/v2/video/call/{type}/{id}/stop_frame_recording", StopFrameRecordingResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.stop_live") def stop_live( self, type: str, @@ -695,7 +698,6 @@ def stop_live( continue_rtmp_broadcasts=continue_rtmp_broadcasts, continue_transcription=continue_transcription, ) - return self.post( "/api/v2/video/call/{type}/{id}/stop_live", StopLiveResponse, @@ -703,6 +705,7 @@ def stop_live( json=json, ) + @telemetry.operation_name("getstream.api.video.stop_recording") def stop_recording( self, type: str, id: str ) -> StreamResponse[StopRecordingResponse]: @@ -710,13 +713,13 @@ def stop_recording( "type": type, "id": id, } - return self.post( "/api/v2/video/call/{type}/{id}/stop_recording", StopRecordingResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.stop_transcription") def stop_transcription( self, type: str, id: str, stop_closed_captions: Optional[bool] = None ) -> StreamResponse[StopTranscriptionResponse]: @@ -725,7 +728,6 @@ def stop_transcription( "id": id, } json = build_body_dict(stop_closed_captions=stop_closed_captions) - return self.post( "/api/v2/video/call/{type}/{id}/stop_transcription", StopTranscriptionResponse, @@ -733,6 +735,7 @@ def stop_transcription( json=json, ) + @telemetry.operation_name("getstream.api.video.list_transcriptions") def list_transcriptions( self, type: str, id: str ) -> StreamResponse[ListTranscriptionsResponse]: @@ -740,13 +743,13 @@ def list_transcriptions( "type": type, "id": id, } - return self.get( "/api/v2/video/call/{type}/{id}/transcriptions", ListTranscriptionsResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.unblock_user") def unblock_user( self, type: str, id: str, user_id: str ) -> StreamResponse[UnblockUserResponse]: @@ -755,7 +758,6 @@ def unblock_user( "id": id, } json = build_body_dict(user_id=user_id) - return self.post( "/api/v2/video/call/{type}/{id}/unblock", UnblockUserResponse, @@ -763,6 +765,7 @@ def unblock_user( json=json, ) + @telemetry.operation_name("getstream.api.video.video_unpin") def video_unpin( self, type: str, id: str, session_id: str, user_id: str ) -> StreamResponse[UnpinResponse]: @@ -771,7 +774,6 @@ def video_unpin( "id": id, } json = build_body_dict(session_id=session_id, user_id=user_id) - return self.post( "/api/v2/video/call/{type}/{id}/unpin", UnpinResponse, @@ -779,6 +781,7 @@ def video_unpin( json=json, ) + @telemetry.operation_name("getstream.api.video.update_user_permissions") def update_user_permissions( self, type: str, @@ -796,7 +799,6 @@ def update_user_permissions( grant_permissions=grant_permissions, revoke_permissions=revoke_permissions, ) - return self.post( "/api/v2/video/call/{type}/{id}/user_permissions", UpdateUserPermissionsResponse, @@ -804,6 +806,7 @@ def update_user_permissions( json=json, ) + @telemetry.operation_name("getstream.api.video.delete_recording") def delete_recording( self, type: str, id: str, session: str, filename: str ) -> StreamResponse[DeleteRecordingResponse]: @@ -813,13 +816,13 @@ def delete_recording( "session": session, "filename": filename, } - return self.delete( "/api/v2/video/call/{type}/{id}/{session}/recordings/{filename}", DeleteRecordingResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.delete_transcription") def delete_transcription( self, type: str, id: str, session: str, filename: str ) -> StreamResponse[DeleteTranscriptionResponse]: @@ -829,13 +832,37 @@ def delete_transcription( "session": session, "filename": filename, } - return self.delete( "/api/v2/video/call/{type}/{id}/{session}/transcriptions/{filename}", DeleteTranscriptionResponse, path_params=path_params, ) + @telemetry.operation_name( + "getstream.api.video.query_call_session_participant_stats" + ) + def query_call_session_participant_stats( + self, + call_type: str, + call_id: str, + session: str, + sort: Optional[List[SortParamRequest]] = None, + filter_conditions: Optional[Dict[str, object]] = None, + ) -> StreamResponse[QueryCallSessionParticipantStatsResponse]: + query_params = build_query_param(sort=sort, filter_conditions=filter_conditions) + path_params = { + "call_type": call_type, + "call_id": call_id, + "session": session, + } + return self.get( + "/api/v2/video/call_stats/{call_type}/{call_id}/{session}/participants", + QueryCallSessionParticipantStatsResponse, + query_params=query_params, + path_params=path_params, + ) + + @telemetry.operation_name("getstream.api.video.query_calls") def query_calls( self, limit: Optional[int] = None, @@ -851,12 +878,13 @@ def query_calls( sort=sort, filter_conditions=filter_conditions, ) - return self.post("/api/v2/video/calls", QueryCallsResponse, json=json) + @telemetry.operation_name("getstream.api.video.list_call_types") def list_call_types(self) -> StreamResponse[ListCallTypeResponse]: return self.get("/api/v2/video/calltypes", ListCallTypeResponse) + @telemetry.operation_name("getstream.api.video.create_call_type") def create_call_type( self, name: str, @@ -872,29 +900,29 @@ def create_call_type( notification_settings=notification_settings, settings=settings, ) - return self.post("/api/v2/video/calltypes", CreateCallTypeResponse, json=json) + @telemetry.operation_name("getstream.api.video.delete_call_type") def delete_call_type(self, name: str) -> StreamResponse[Response]: path_params = { "name": name, } - return self.delete( "/api/v2/video/calltypes/{name}", Response, path_params=path_params ) + @telemetry.operation_name("getstream.api.video.get_call_type") def get_call_type(self, name: str) -> StreamResponse[GetCallTypeResponse]: path_params = { "name": name, } - return self.get( "/api/v2/video/calltypes/{name}", GetCallTypeResponse, path_params=path_params, ) + @telemetry.operation_name("getstream.api.video.update_call_type") def update_call_type( self, name: str, @@ -912,7 +940,6 @@ def update_call_type( notification_settings=notification_settings, settings=settings, ) - return self.put( "/api/v2/video/calltypes/{name}", UpdateCallTypeResponse, @@ -920,9 +947,11 @@ def update_call_type( json=json, ) + @telemetry.operation_name("getstream.api.video.get_edges") def get_edges(self) -> StreamResponse[GetEdgesResponse]: return self.get("/api/v2/video/edges", GetEdgesResponse) + @telemetry.operation_name("getstream.api.video.query_aggregate_call_stats") def query_aggregate_call_stats( self, _from: Optional[str] = None, @@ -930,7 +959,6 @@ def query_aggregate_call_stats( report_types: Optional[List[str]] = None, ) -> StreamResponse[QueryAggregateCallStatsResponse]: json = build_body_dict(_from=_from, to=to, report_types=report_types) - return self.post( "/api/v2/video/stats", QueryAggregateCallStatsResponse, json=json ) From 0d634fed908bb14256a6cb762801a52033a14528 Mon Sep 17 00:00:00 2001 From: Tommaso Barbugli Date: Sat, 27 Sep 2025 17:35:30 +0200 Subject: [PATCH 03/12] wip --- getstream/base.py | 109 ++++++++++-------------- getstream/common/telemetry.py | 25 ++++++ getstream/video/async_call.py | 2 +- getstream/video/call.py | 2 +- pyproject.toml | 2 + tests/test_metrics_prometheus_manual.py | 87 +++++++++++++++++++ tests/test_video_examples.py | 8 +- uv.lock | 27 ++++++ 8 files changed, 190 insertions(+), 72 deletions(-) create mode 100644 tests/test_metrics_prometheus_manual.py diff --git a/getstream/base.py b/getstream/base.py index 7b600ee2..9d939fe9 100644 --- a/getstream/base.py +++ b/getstream/base.py @@ -1,6 +1,5 @@ import json import time -import inspect from typing import Any, Dict, Optional, Type, get_origin from getstream.models import APIError @@ -16,6 +15,7 @@ record_metrics, span_request, current_operation, + metric_attributes, ) @@ -54,7 +54,38 @@ def _parse_response( return StreamResponse(response, data) -class BaseClient(BaseConfig, ResponseParserMixin, ABC): +class TelemetryEndpointMixin: + def _normalize_endpoint_from_path(self, path: str) -> str: + # Convert /api/v2/video/call/{type}/{id} -> api.v2.video.call.$type.$id + norm_parts = [] + for p in path.strip("/").split("/"): + if not p: + continue + if p.startswith("{") and p.endswith("}"): + name = p[1:-1].strip() + if name: + norm_parts.append(f"${name}") + else: + norm_parts.append(p) + return ".".join(norm_parts) if norm_parts else "root" + + def _prepare_request(self, method: str, path: str, query_params, kwargs): + path_params = kwargs.get("path_params") if kwargs else None + url_path = ( + build_path(path, path_params) if path_params else build_path(path, None) + ) + url_full = f"{self.base_url}{url_path}" + endpoint = self._endpoint_name(path) + span_attrs = common_attributes( + api_key=self.api_key, + endpoint=endpoint, + method=method, + url=url_full, + ) + return url_path, url_full, endpoint, span_attrs + + +class BaseClient(TelemetryEndpointMixin, BaseConfig, ResponseParserMixin, ABC): def __init__( self, api_key, @@ -81,46 +112,18 @@ def __enter__(self): def __exit__(self, exc_type, exc_val, exc_tb): self.close() - def _normalize_endpoint_from_path(self, path: str) -> str: - # Convert /api/v2/video/call/{type}/{id} -> api.v2.video.call.$type.$id - norm_parts = [] - for p in path.strip("/").split("/"): - if not p: - continue - if p.startswith("{") and p.endswith("}"): - name = p[1:-1].strip() - if name: - norm_parts.append(f"${name}") - else: - norm_parts.append(p) - return ".".join(norm_parts) if norm_parts else "root" - def _endpoint_name(self, path: str) -> str: op = current_operation(None) if op: return op - try: - frame = inspect.currentframe() - caller = frame.f_back # BaseClient. - op = caller.f_back.f_code.co_name if caller and caller.f_back else None - except Exception: - op = None - if op: - return f"{self.__class__.__name__}.{op}" return self._normalize_endpoint_from_path(path) def _request_sync( self, method: str, path: str, *, query_params=None, args=(), kwargs=None ): kwargs = kwargs or {} - path_params = kwargs.get("path_params") - url_path = ( - build_path(path, path_params) if path_params else build_path(path, None) - ) - url_full = f"{self.base_url}{url_path}" - endpoint = self._endpoint_name(path) - attrs = common_attributes( - api_key=self.api_key, endpoint=endpoint, method=method, url=url_full + url_path, url_full, endpoint, attrs = self._prepare_request( + method, path, query_params, kwargs ) start = time.perf_counter() # Span name uses logical operation (endpoint) rather than raw HTTP @@ -139,11 +142,11 @@ def _request_sync( except Exception: pass duration_ms = (time.perf_counter() - start) * 1000.0 - metric_attrs = common_attributes( + # Metrics should be low-cardinality: exclude url/call_cid/channel_cid + metric_attrs = metric_attributes( api_key=self.api_key, endpoint=endpoint, method=method, - url=url_full, status_code=getattr(response, "status_code", None), ) record_metrics(duration_ms, attributes=metric_attrs) @@ -246,7 +249,7 @@ def close(self): self.client.close() -class AsyncBaseClient(BaseConfig, ResponseParserMixin, ABC): +class AsyncBaseClient(TelemetryEndpointMixin, BaseConfig, ResponseParserMixin, ABC): def __init__( self, api_key, @@ -277,42 +280,18 @@ async def aclose(self): """Close HTTPX async client (closes pools/keep-alives).""" await self.client.aclose() - def _normalize_endpoint_from_path(self, path: str) -> str: - norm_parts = [] - for p in path.strip("/").split("/"): - if not p: - continue - if p.startswith("{") and p.endswith("}"): - name = p[1:-1].strip() - if name: - norm_parts.append(f"${name}") - else: - norm_parts.append(p) - return ".".join(norm_parts) if norm_parts else "root" - def _endpoint_name(self, path: str) -> str: - try: - frame = inspect.currentframe() - caller = frame.f_back - op = caller.f_back.f_code.co_name if caller and caller.f_back else None - except Exception: - op = None + op = current_operation(None) if op: - return f"{self.__class__.__name__}.{op}" + return op return self._normalize_endpoint_from_path(path) async def _request_async( self, method: str, path: str, *, query_params=None, args=(), kwargs=None ): kwargs = kwargs or {} - path_params = kwargs.get("path_params") - url_path = ( - build_path(path, path_params) if path_params else build_path(path, None) - ) - url_full = f"{self.base_url}{url_path}" - endpoint = self._endpoint_name(path) - attrs = common_attributes( - api_key=self.api_key, endpoint=endpoint, method=method, url=url_full + url_path, url_full, endpoint, attrs = self._prepare_request( + method, path, query_params, kwargs ) start = time.perf_counter() with span_request( @@ -330,11 +309,11 @@ async def _request_async( except Exception: pass duration_ms = (time.perf_counter() - start) * 1000.0 - metric_attrs = common_attributes( + # Metrics should be low-cardinality: exclude url/call_cid/channel_cid + metric_attrs = metric_attributes( api_key=self.api_key, endpoint=endpoint, method=method, - url=url_full, status_code=getattr(response, "status_code", None), ) record_metrics(duration_ms, attributes=metric_attrs) diff --git a/getstream/common/telemetry.py b/getstream/common/telemetry.py index 1018fe0c..caab9450 100644 --- a/getstream/common/telemetry.py +++ b/getstream/common/telemetry.py @@ -125,6 +125,31 @@ def common_attributes( return attrs +def metric_attributes( + *, + api_key: Optional[str], + endpoint: str, + method: str, + status_code: Optional[int] = None, +) -> Dict[str, Any]: + """Build low-cardinality metric attributes. + + - Excludes url.full + - Excludes stream.call_cid and stream.channel_cid + - Keeps redacted stream.api_key, endpoint, method, status_code + """ + attrs: Dict[str, Any] = { + "stream.endpoint": endpoint, + "http.request.method": method, + } + if status_code is not None: + attrs["http.response.status_code"] = int(status_code) + red = redact_api_key(api_key) + if red: + attrs["stream.api_key"] = red + return attrs + + def record_metrics(duration_ms: float, *, attributes: Dict[str, Any]) -> None: if not _HAS_OTEL or REQ_HIST is None or REQ_COUNT is None: return diff --git a/getstream/video/async_call.py b/getstream/video/async_call.py index b9eb581f..febe1705 100644 --- a/getstream/video/async_call.py +++ b/getstream/video/async_call.py @@ -2,7 +2,7 @@ from getstream.common.telemetry import attach_call_cid_async from getstream.models import * from getstream.stream_response import StreamResponse -from video import BaseCall +from getstream.video import BaseCall class Call(BaseCall): diff --git a/getstream/video/call.py b/getstream/video/call.py index 45829600..2c201ca3 100644 --- a/getstream/video/call.py +++ b/getstream/video/call.py @@ -2,7 +2,7 @@ from getstream.common.telemetry import attach_call_cid from getstream.models import * from getstream.stream_response import StreamResponse -from video import BaseCall +from getstream.video import BaseCall class Call(BaseCall): diff --git a/pyproject.toml b/pyproject.toml index a8941bcc..814bc81e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,6 +74,8 @@ dev = [ "mypy-protobuf==3.5.0", "pytest-timeout>=2.3.1", "opentelemetry-exporter-otlp>=1.37.0", + "opentelemetry-exporter-prometheus>=0.58b0", + "prometheus-client>=0.23.1", ] [tool.uv.workspace] diff --git a/tests/test_metrics_prometheus_manual.py b/tests/test_metrics_prometheus_manual.py new file mode 100644 index 00000000..14513b93 --- /dev/null +++ b/tests/test_metrics_prometheus_manual.py @@ -0,0 +1,87 @@ +import os +import time +import uuid +import pytest + + +@pytest.mark.integration +def test_metrics_prometheus_manual(): + """ + Manual metrics check with Prometheus (real API calls). + + This test configures an in-process Prometheus exporter and serves metrics + on http://localhost:9464/metrics. It then performs a few Stream API calls + (both success and intentional failures) so you can inspect exported + metrics in Prometheus format. + + Prerequisites: + - Set Stream credentials in your environment (or .env) + export STREAM_API_KEY=xxxx + export STREAM_API_SECRET=yyyy + # Optional (defaults to production): export STREAM_BASE_URL=https://chat.stream-io-api.com/ + + - Install deps: + pip install getstream[telemetry] opentelemetry-exporter-prometheus prometheus_client + + Run locally: + uv run pytest -q -k metrics_prometheus_manual -s + + Inspect metrics: + curl http://localhost:9464/metrics | rg getstream_client_request + # or open in a browser: http://localhost:9464/metrics + + Notes: + - Metric names exposed by this SDK: + getstream.client.request.duration (histogram) + getstream.client.request.count (counter) + Prometheus exporter renders them as: + getstream_client_request_duration_* and getstream_client_request_count_total + - Attributes like stream.endpoint, http.response.status_code, and + stream.call_cid/stream.channel_cid (when present) appear as labels. + """ + + try: + from opentelemetry import metrics + from opentelemetry.sdk.resources import Resource + from opentelemetry.sdk.metrics import MeterProvider + from opentelemetry.exporter.prometheus import PrometheusMetricReader + from prometheus_client import start_http_server + except Exception: + pytest.skip( + "Missing Prometheus exporter; install: pip install getstream[telemetry] opentelemetry-exporter-prometheus prometheus_client" + ) + + # Start Prometheus exporter on :9464 + resource = Resource.create( + { + "service.name": "getstream-metrics-manual", + "deployment.environment": os.getenv("DEPLOYMENT_ENV", "dev"), + } + ) + reader = PrometheusMetricReader() + metrics.set_meter_provider( + MeterProvider(resource=resource, metric_readers=[reader]) + ) + start_http_server(port=9464) + + # Create real Stream client (reads STREAM_* from env if not provided) + from getstream import Stream + from getstream.base import StreamAPIException + + client = Stream() # relies on env vars via pydantic Settings + + # Make a few calls — some will succeed, some fail — to populate metrics + # Success (read-only) + try: + client.get_app() + client.video.list_call_types() + client.block_users(blocked_user_id="nonexistent", user_id="nonexistent") + client.delete_users( + user_ids=[f"manual-{uuid.uuid4().hex[:8]}"], messages="hard" + ) + except StreamAPIException: + pass + + print("Prometheus metrics available at: http://localhost:9464/metrics") + # Keep process alive briefly so you can curl/visit the endpoint during test run + time.sleep(20) diff --git a/tests/test_video_examples.py b/tests/test_video_examples.py index 9f97a4c1..113212c1 100644 --- a/tests/test_video_examples.py +++ b/tests/test_video_examples.py @@ -582,9 +582,9 @@ def handler(request: httpx.Request) -> httpx.Response: spans = span_exporter.get_finished_spans() assert len(spans) >= 1 s = spans[-1] - # Endpoint should reference the client; method may be the RestClient method name fallback + # Endpoint should be the normalized path if no logical operation is set endpoint_attr = s.attributes.get("stream.endpoint") - assert isinstance(endpoint_attr, str) and endpoint_attr.startswith("DummyClient.") + assert isinstance(endpoint_attr, str) and endpoint_attr.endswith("ping") assert s.attributes.get("http.request.method") == "GET" assert s.attributes.get("http.response.status_code") == 200 # API key is redacted @@ -611,9 +611,7 @@ def handler(request: httpx.Request) -> httpx.Response: "getstream.client.request.duration", "getstream.client.request.count", }.issubset(names_seen) - assert any( - isinstance(ep, str) and ep.startswith("DummyClient.") for ep in endpoints - ) + assert any(isinstance(ep, str) and ep.endswith("ping") for ep in endpoints) def test_otel_baggage_call_cid_video(monkeypatch): diff --git a/uv.lock b/uv.lock index 229d4c4a..807ca8b7 100644 --- a/uv.lock +++ b/uv.lock @@ -672,7 +672,9 @@ dev = [ { name = "grpcio-tools" }, { name = "mypy-protobuf" }, { name = "opentelemetry-exporter-otlp" }, + { name = "opentelemetry-exporter-prometheus" }, { name = "pre-commit" }, + { name = "prometheus-client" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-timeout" }, @@ -721,7 +723,9 @@ dev = [ { name = "grpcio-tools", specifier = ">=1.73.1" }, { name = "mypy-protobuf", specifier = "==3.5.0" }, { name = "opentelemetry-exporter-otlp", specifier = ">=1.37.0" }, + { name = "opentelemetry-exporter-prometheus", specifier = ">=0.58b0" }, { name = "pre-commit", specifier = ">=4.2.0" }, + { name = "prometheus-client", specifier = ">=0.23.1" }, { name = "pytest", specifier = ">=8.4.1" }, { name = "pytest-asyncio", specifier = ">=1.0.0" }, { name = "pytest-timeout", specifier = ">=2.3.1" }, @@ -1572,6 +1576,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/e9/70d74a664d83976556cec395d6bfedd9b85ec1498b778367d5f93e373397/opentelemetry_exporter_otlp_proto_http-1.37.0-py3-none-any.whl", hash = "sha256:54c42b39945a6cc9d9a2a33decb876eabb9547e0dcb49df090122773447f1aef", size = 19576 }, ] +[[package]] +name = "opentelemetry-exporter-prometheus" +version = "0.58b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, + { name = "prometheus-client" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/4c/e351559526ee35fa36d990d3455e81a5607c1fa3e544b599ad802f2481f8/opentelemetry_exporter_prometheus-0.58b0.tar.gz", hash = "sha256:70f2627b4bb82bac65a1fcf95f6e0dcce9e823dd47379ced854753a7e14dfc93", size = 14972 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/e3/50e9cdc5a52c2ab19585dd69e668ec9fee0343fafc4bffa919ca79230a4f/opentelemetry_exporter_prometheus-0.58b0-py3-none-any.whl", hash = "sha256:02005033a7a108ab9f3000ff3aa49e2d03a8893b5bf3431322ffa246affbf951", size = 13016 }, +] + [[package]] name = "opentelemetry-proto" version = "1.37.0" @@ -1663,6 +1681,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965 }, ] +[[package]] +name = "prometheus-client" +version = "0.23.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/53/3edb5d68ecf6b38fcbcc1ad28391117d2a322d9a1a3eff04bfdb184d8c3b/prometheus_client-0.23.1.tar.gz", hash = "sha256:6ae8f9081eaaaf153a2e959d2e6c4f4fb57b12ef76c8c7980202f1e57b48b2ce", size = 80481 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl", hash = "sha256:dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99", size = 61145 }, +] + [[package]] name = "propcache" version = "0.3.2" From ad3581ab54d9cb8d5ffdf6f3f5e6b1325a15bb75 Mon Sep 17 00:00:00 2001 From: Tommaso Barbugli Date: Sat, 27 Sep 2025 17:37:28 +0200 Subject: [PATCH 04/12] wip --- getstream/chat/async_channel.py | 24 ++++++++++++++---------- getstream/chat/async_rest_client.py | 24 ++++++++++++------------ getstream/chat/channel.py | 24 ++++++++++++++---------- getstream/chat/rest_client.py | 24 ++++++++++++------------ 4 files changed, 52 insertions(+), 44 deletions(-) diff --git a/getstream/chat/async_channel.py b/getstream/chat/async_channel.py index 3da906df..9c3c9964 100644 --- a/getstream/chat/async_channel.py +++ b/getstream/chat/async_channel.py @@ -126,18 +126,20 @@ async def send_event(self, event: EventRequest) -> StreamResponse[EventResponse] return response @attach_channel_cid_async - async def delete_file(self, url: Optional[str] = None) -> StreamResponse[Response]: - response = await self.client.delete_file( + async def delete_channel_file( + self, url: Optional[str] = None + ) -> StreamResponse[Response]: + response = await self.client.delete_channel_file( type=self.channel_type, id=self.channel_id, url=url ) self._sync_from_response(response.data) return response @attach_channel_cid_async - async def upload_file( + async def upload_channel_file( self, file: Optional[str] = None, user: Optional[OnlyUserID] = None - ) -> StreamResponse[FileUploadResponse]: - response = await self.client.upload_file( + ) -> StreamResponse[UploadChannelFileResponse]: + response = await self.client.upload_channel_file( type=self.channel_type, id=self.channel_id, file=file, user=user ) self._sync_from_response(response.data) @@ -161,21 +163,23 @@ async def hide( return response @attach_channel_cid_async - async def delete_image(self, url: Optional[str] = None) -> StreamResponse[Response]: - response = await self.client.delete_image( + async def delete_channel_image( + self, url: Optional[str] = None + ) -> StreamResponse[Response]: + response = await self.client.delete_channel_image( type=self.channel_type, id=self.channel_id, url=url ) self._sync_from_response(response.data) return response @attach_channel_cid_async - async def upload_image( + async def upload_channel_image( self, file: Optional[str] = None, upload_sizes: Optional[List[ImageSize]] = None, user: Optional[OnlyUserID] = None, - ) -> StreamResponse[ImageUploadResponse]: - response = await self.client.upload_image( + ) -> StreamResponse[UploadChannelResponse]: + response = await self.client.upload_channel_image( type=self.channel_type, id=self.channel_id, file=file, diff --git a/getstream/chat/async_rest_client.py b/getstream/chat/async_rest_client.py index 9b95ddf0..82661b3f 100644 --- a/getstream/chat/async_rest_client.py +++ b/getstream/chat/async_rest_client.py @@ -322,8 +322,8 @@ async def send_event( json=json, ) - @telemetry.operation_name("getstream.api.chat.delete_file") - async def delete_file( + @telemetry.operation_name("getstream.api.chat.delete_channel_file") + async def delete_channel_file( self, type: str, id: str, url: Optional[str] = None ) -> StreamResponse[Response]: query_params = build_query_param(url=url) @@ -338,14 +338,14 @@ async def delete_file( path_params=path_params, ) - @telemetry.operation_name("getstream.api.chat.upload_file") - async def upload_file( + @telemetry.operation_name("getstream.api.chat.upload_channel_file") + async def upload_channel_file( self, type: str, id: str, file: Optional[str] = None, user: Optional[OnlyUserID] = None, - ) -> StreamResponse[FileUploadResponse]: + ) -> StreamResponse[UploadChannelFileResponse]: path_params = { "type": type, "id": id, @@ -353,7 +353,7 @@ async def upload_file( json = build_body_dict(file=file, user=user) return await self.post( "/api/v2/chat/channels/{type}/{id}/file", - FileUploadResponse, + UploadChannelFileResponse, path_params=path_params, json=json, ) @@ -379,8 +379,8 @@ async def hide_channel( json=json, ) - @telemetry.operation_name("getstream.api.chat.delete_image") - async def delete_image( + @telemetry.operation_name("getstream.api.chat.delete_channel_image") + async def delete_channel_image( self, type: str, id: str, url: Optional[str] = None ) -> StreamResponse[Response]: query_params = build_query_param(url=url) @@ -395,15 +395,15 @@ async def delete_image( path_params=path_params, ) - @telemetry.operation_name("getstream.api.chat.upload_image") - async def upload_image( + @telemetry.operation_name("getstream.api.chat.upload_channel_image") + async def upload_channel_image( self, type: str, id: str, file: Optional[str] = None, upload_sizes: Optional[List[ImageSize]] = None, user: Optional[OnlyUserID] = None, - ) -> StreamResponse[ImageUploadResponse]: + ) -> StreamResponse[UploadChannelResponse]: path_params = { "type": type, "id": id, @@ -411,7 +411,7 @@ async def upload_image( json = build_body_dict(file=file, upload_sizes=upload_sizes, user=user) return await self.post( "/api/v2/chat/channels/{type}/{id}/image", - ImageUploadResponse, + UploadChannelResponse, path_params=path_params, json=json, ) diff --git a/getstream/chat/channel.py b/getstream/chat/channel.py index 1cd35e0b..f64841cb 100644 --- a/getstream/chat/channel.py +++ b/getstream/chat/channel.py @@ -126,18 +126,20 @@ def send_event(self, event: EventRequest) -> StreamResponse[EventResponse]: return response @attach_channel_cid - def delete_file(self, url: Optional[str] = None) -> StreamResponse[Response]: - response = self.client.delete_file( + def delete_channel_file( + self, url: Optional[str] = None + ) -> StreamResponse[Response]: + response = self.client.delete_channel_file( type=self.channel_type, id=self.channel_id, url=url ) self._sync_from_response(response.data) return response @attach_channel_cid - def upload_file( + def upload_channel_file( self, file: Optional[str] = None, user: Optional[OnlyUserID] = None - ) -> StreamResponse[FileUploadResponse]: - response = self.client.upload_file( + ) -> StreamResponse[UploadChannelFileResponse]: + response = self.client.upload_channel_file( type=self.channel_type, id=self.channel_id, file=file, user=user ) self._sync_from_response(response.data) @@ -161,21 +163,23 @@ def hide( return response @attach_channel_cid - def delete_image(self, url: Optional[str] = None) -> StreamResponse[Response]: - response = self.client.delete_image( + def delete_channel_image( + self, url: Optional[str] = None + ) -> StreamResponse[Response]: + response = self.client.delete_channel_image( type=self.channel_type, id=self.channel_id, url=url ) self._sync_from_response(response.data) return response @attach_channel_cid - def upload_image( + def upload_channel_image( self, file: Optional[str] = None, upload_sizes: Optional[List[ImageSize]] = None, user: Optional[OnlyUserID] = None, - ) -> StreamResponse[ImageUploadResponse]: - response = self.client.upload_image( + ) -> StreamResponse[UploadChannelResponse]: + response = self.client.upload_channel_image( type=self.channel_type, id=self.channel_id, file=file, diff --git a/getstream/chat/rest_client.py b/getstream/chat/rest_client.py index c371706c..f981823e 100644 --- a/getstream/chat/rest_client.py +++ b/getstream/chat/rest_client.py @@ -318,8 +318,8 @@ def send_event( json=json, ) - @telemetry.operation_name("getstream.api.chat.delete_file") - def delete_file( + @telemetry.operation_name("getstream.api.chat.delete_channel_file") + def delete_channel_file( self, type: str, id: str, url: Optional[str] = None ) -> StreamResponse[Response]: query_params = build_query_param(url=url) @@ -334,14 +334,14 @@ def delete_file( path_params=path_params, ) - @telemetry.operation_name("getstream.api.chat.upload_file") - def upload_file( + @telemetry.operation_name("getstream.api.chat.upload_channel_file") + def upload_channel_file( self, type: str, id: str, file: Optional[str] = None, user: Optional[OnlyUserID] = None, - ) -> StreamResponse[FileUploadResponse]: + ) -> StreamResponse[UploadChannelFileResponse]: path_params = { "type": type, "id": id, @@ -349,7 +349,7 @@ def upload_file( json = build_body_dict(file=file, user=user) return self.post( "/api/v2/chat/channels/{type}/{id}/file", - FileUploadResponse, + UploadChannelFileResponse, path_params=path_params, json=json, ) @@ -375,8 +375,8 @@ def hide_channel( json=json, ) - @telemetry.operation_name("getstream.api.chat.delete_image") - def delete_image( + @telemetry.operation_name("getstream.api.chat.delete_channel_image") + def delete_channel_image( self, type: str, id: str, url: Optional[str] = None ) -> StreamResponse[Response]: query_params = build_query_param(url=url) @@ -391,15 +391,15 @@ def delete_image( path_params=path_params, ) - @telemetry.operation_name("getstream.api.chat.upload_image") - def upload_image( + @telemetry.operation_name("getstream.api.chat.upload_channel_image") + def upload_channel_image( self, type: str, id: str, file: Optional[str] = None, upload_sizes: Optional[List[ImageSize]] = None, user: Optional[OnlyUserID] = None, - ) -> StreamResponse[ImageUploadResponse]: + ) -> StreamResponse[UploadChannelResponse]: path_params = { "type": type, "id": id, @@ -407,7 +407,7 @@ def upload_image( json = build_body_dict(file=file, upload_sizes=upload_sizes, user=user) return self.post( "/api/v2/chat/channels/{type}/{id}/image", - ImageUploadResponse, + UploadChannelResponse, path_params=path_params, json=json, ) From 1efc23f86c3bfb9f31db2aa061e0a99192719945 Mon Sep 17 00:00:00 2001 From: Tommaso Barbugli Date: Sat, 27 Sep 2025 17:45:08 +0200 Subject: [PATCH 05/12] remove useless redact key --- getstream/common/telemetry.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/getstream/common/telemetry.py b/getstream/common/telemetry.py index caab9450..8ffe60be 100644 --- a/getstream/common/telemetry.py +++ b/getstream/common/telemetry.py @@ -38,6 +38,11 @@ "authorization,password,token,secret,api_key,authorization_bearer,auth", ).split(",") } +REDACT_API_KEY = os.getenv("STREAM_OTEL_REDACT_API_KEY", "true").lower() in { + "1", + "true", + "yes", +} def _noop_cm(): # pragma: no cover - used when OTel missing @@ -67,14 +72,6 @@ def _inner(*_args, **_kwargs): REQ_COUNT = None -def redact_api_key(api_key: str | None) -> str | None: - if not api_key: - return None - if len(api_key) <= 6: - return "***" - return f"{api_key[:6]}***" - - def safe_dump(payload: Any, max_chars: int | None = None) -> str: if max_chars is None: max_chars = MAX_BODY_CHARS @@ -112,9 +109,8 @@ def common_attributes( attrs["url.full"] = url if status_code is not None: attrs["http.response.status_code"] = int(status_code) - red = redact_api_key(api_key) - if red: - attrs["stream.api_key"] = red + if api_key: + attrs["stream.api_key"] = api_key if _HAS_OTEL and baggage is not None: call_cid = baggage.get_baggage("stream.call_cid") if call_cid: @@ -144,9 +140,8 @@ def metric_attributes( } if status_code is not None: attrs["http.response.status_code"] = int(status_code) - red = redact_api_key(api_key) - if red: - attrs["stream.api_key"] = red + if api_key: + attrs["stream.api_key"] = api_key return attrs From 817c35b020756f7ef566080bba193f084c6f9ae7 Mon Sep 17 00:00:00 2001 From: Tommaso Barbugli Date: Sat, 27 Sep 2025 18:05:26 +0200 Subject: [PATCH 06/12] wip --- getstream/models/__init__.py | 1167 ++++------------------- tests/test_metrics_prometheus_manual.py | 41 +- 2 files changed, 225 insertions(+), 983 deletions(-) diff --git a/getstream/models/__init__.py b/getstream/models/__init__.py index a3593ee6..6835a9a2 100644 --- a/getstream/models/__init__.py +++ b/getstream/models/__init__.py @@ -202,36 +202,6 @@ class Action(DataClassJsonMixin): ) -@dataclass -class ActionLog(DataClassJsonMixin): - created_at: datetime = dc_field( - metadata=dc_config( - field_name="created_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ) - ) - id: str = dc_field(metadata=dc_config(field_name="id")) - reason: str = dc_field(metadata=dc_config(field_name="reason")) - reporter_type: str = dc_field(metadata=dc_config(field_name="reporter_type")) - review_queue_item_id: str = dc_field( - metadata=dc_config(field_name="review_queue_item_id") - ) - target_user_id: str = dc_field(metadata=dc_config(field_name="target_user_id")) - type: str = dc_field(metadata=dc_config(field_name="type")) - custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) - review_queue_item: "Optional[ReviewQueueItem]" = dc_field( - default=None, metadata=dc_config(field_name="review_queue_item") - ) - target_user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="target_user") - ) - user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") - ) - - @dataclass class ActionLogResponse(DataClassJsonMixin): created_at: datetime = dc_field( @@ -247,6 +217,7 @@ class ActionLogResponse(DataClassJsonMixin): target_user_id: str = dc_field(metadata=dc_config(field_name="target_user_id")) type: str = dc_field(metadata=dc_config(field_name="type")) user_id: str = dc_field(metadata=dc_config(field_name="user_id")) + ai_providers: List[str] = dc_field(metadata=dc_config(field_name="ai_providers")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) review_queue_item: "Optional[ReviewQueueItemResponse]" = dc_field( default=None, metadata=dc_config(field_name="review_queue_item") @@ -1459,8 +1430,7 @@ class AsyncExportErrorEvent(DataClassJsonMixin): task_id: str = dc_field(metadata=dc_config(field_name="task_id")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) type: str = dc_field( - default="export.bulk_image_moderation.error", - metadata=dc_config(field_name="type"), + default="export.channels.error", metadata=dc_config(field_name="type") ) received_at: Optional[datetime] = dc_field( default=None, @@ -2361,117 +2331,6 @@ class BulkImageModerationResponse(DataClassJsonMixin): task_id: str = dc_field(metadata=dc_config(field_name="task_id")) -@dataclass -class Call(DataClassJsonMixin): - app_pk: int = dc_field(metadata=dc_config(field_name="AppPK")) - backstage: bool = dc_field(metadata=dc_config(field_name="Backstage")) - channel_cid: str = dc_field(metadata=dc_config(field_name="ChannelCID")) - cid: str = dc_field(metadata=dc_config(field_name="CID")) - created_at: datetime = dc_field( - metadata=dc_config( - field_name="CreatedAt", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ) - ) - created_by_user_id: str = dc_field(metadata=dc_config(field_name="CreatedByUserID")) - current_session_id: str = dc_field( - metadata=dc_config(field_name="CurrentSessionID") - ) - id: str = dc_field(metadata=dc_config(field_name="ID")) - last_session_id: str = dc_field(metadata=dc_config(field_name="LastSessionID")) - team: str = dc_field(metadata=dc_config(field_name="Team")) - thumbnail_url: str = dc_field(metadata=dc_config(field_name="ThumbnailURL")) - type: str = dc_field(metadata=dc_config(field_name="Type")) - updated_at: datetime = dc_field( - metadata=dc_config( - field_name="UpdatedAt", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ) - ) - blocked_user_i_ds: List[str] = dc_field( - metadata=dc_config(field_name="BlockedUserIDs") - ) - blocked_users: "List[User]" = dc_field( - metadata=dc_config(field_name="BlockedUsers") - ) - egresses: "List[CallEgress]" = dc_field(metadata=dc_config(field_name="Egresses")) - members: "List[CallMember]" = dc_field(metadata=dc_config(field_name="Members")) - custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="Custom")) - deleted_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="DeletedAt", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - egress_updated_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="EgressUpdatedAt", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - ended_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="EndedAt", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - join_ahead_time_seconds: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="JoinAheadTimeSeconds") - ) - last_heartbeat_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="LastHeartbeatAt", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - member_count: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="MemberCount") - ) - starts_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="StartsAt", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - call_type: "Optional[CallType]" = dc_field( - default=None, metadata=dc_config(field_name="CallType") - ) - created_by: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="CreatedBy") - ) - member_lookup: "Optional[MemberLookup]" = dc_field( - default=None, metadata=dc_config(field_name="MemberLookup") - ) - session: "Optional[CallSession]" = dc_field( - default=None, metadata=dc_config(field_name="Session") - ) - settings: "Optional[CallSettings]" = dc_field( - default=None, metadata=dc_config(field_name="Settings") - ) - settings_overrides: "Optional[CallSettings]" = dc_field( - default=None, metadata=dc_config(field_name="SettingsOverrides") - ) - - @dataclass class CallAcceptedEvent(DataClassJsonMixin): call_cid: str = dc_field(metadata=dc_config(field_name="call_cid")) @@ -2610,45 +2469,6 @@ class CallDurationReportResponse(DataClassJsonMixin): ) -@dataclass -class CallEgress(DataClassJsonMixin): - app_pk: int = dc_field(metadata=dc_config(field_name="app_pk")) - call_id: str = dc_field(metadata=dc_config(field_name="call_id")) - call_type: str = dc_field(metadata=dc_config(field_name="call_type")) - egress_id: str = dc_field(metadata=dc_config(field_name="egress_id")) - egress_type: str = dc_field(metadata=dc_config(field_name="egress_type")) - instance_ip: str = dc_field(metadata=dc_config(field_name="instance_ip")) - started_at: datetime = dc_field( - metadata=dc_config( - field_name="started_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ) - ) - state: str = dc_field(metadata=dc_config(field_name="state")) - updated_at: datetime = dc_field( - metadata=dc_config( - field_name="updated_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ) - ) - stopped_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="stopped_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - config: "Optional[EgressTaskConfig]" = dc_field( - default=None, metadata=dc_config(field_name="config") - ) - - @dataclass class CallEndedEvent(DataClassJsonMixin): call_cid: str = dc_field(metadata=dc_config(field_name="call_cid")) @@ -2827,41 +2647,6 @@ class CallLiveStartedEvent(DataClassJsonMixin): ) -@dataclass -class CallMember(DataClassJsonMixin): - created_at: datetime = dc_field( - metadata=dc_config( - field_name="created_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ) - ) - role: str = dc_field(metadata=dc_config(field_name="role")) - updated_at: datetime = dc_field( - metadata=dc_config( - field_name="updated_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ) - ) - user_id: str = dc_field(metadata=dc_config(field_name="user_id")) - custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) - deleted_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="deleted_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") - ) - - @dataclass class CallMemberAddedEvent(DataClassJsonMixin): call_cid: str = dc_field(metadata=dc_config(field_name="call_cid")) @@ -3013,113 +2798,6 @@ class CallNotificationEvent(DataClassJsonMixin): ) -@dataclass -class CallParticipant(DataClassJsonMixin): - banned: bool = dc_field(metadata=dc_config(field_name="banned")) - id: str = dc_field(metadata=dc_config(field_name="id")) - joined_at: datetime = dc_field( - metadata=dc_config( - field_name="JoinedAt", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ) - ) - online: bool = dc_field(metadata=dc_config(field_name="online")) - role: str = dc_field(metadata=dc_config(field_name="role")) - role: str = dc_field(metadata=dc_config(field_name="Role")) - user_session_id: str = dc_field(metadata=dc_config(field_name="UserSessionID")) - custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) - teams_role: "Dict[str, str]" = dc_field(metadata=dc_config(field_name="teams_role")) - avg_response_time: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="avg_response_time") - ) - ban_expires: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="ban_expires", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - created_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="created_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - deactivated_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="deactivated_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - deleted_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="deleted_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - invisible: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="invisible") - ) - language: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="language") - ) - last_active: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="last_active", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - last_engaged_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="last_engaged_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - revoke_tokens_issued_before: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="revoke_tokens_issued_before", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - updated_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="updated_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - teams: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="teams") - ) - privacy_settings: "Optional[PrivacySettings]" = dc_field( - default=None, metadata=dc_config(field_name="privacy_settings") - ) - - @dataclass class CallParticipantCountReport(DataClassJsonMixin): histogram: "List[ReportByHistogramBucket]" = dc_field( @@ -3371,230 +3049,111 @@ class CallResponse(DataClassJsonMixin): ) created_by: "UserResponse" = dc_field(metadata=dc_config(field_name="created_by")) custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) - egress: "EgressResponse" = dc_field(metadata=dc_config(field_name="egress")) - ingress: "CallIngressResponse" = dc_field(metadata=dc_config(field_name="ingress")) - settings: "CallSettingsResponse" = dc_field( - metadata=dc_config(field_name="settings") - ) - channel_cid: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="channel_cid") - ) - ended_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="ended_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - join_ahead_time_seconds: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="join_ahead_time_seconds") - ) - starts_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="starts_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) - session: "Optional[CallSessionResponse]" = dc_field( - default=None, metadata=dc_config(field_name="session") - ) - thumbnails: "Optional[ThumbnailResponse]" = dc_field( - default=None, metadata=dc_config(field_name="thumbnails") - ) - - -@dataclass -class CallRingEvent(DataClassJsonMixin): - call_cid: str = dc_field(metadata=dc_config(field_name="call_cid")) - created_at: datetime = dc_field( - metadata=dc_config( - field_name="created_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ) - ) - session_id: str = dc_field(metadata=dc_config(field_name="session_id")) - video: bool = dc_field(metadata=dc_config(field_name="video")) - members: "List[MemberResponse]" = dc_field(metadata=dc_config(field_name="members")) - call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) - user: "UserResponse" = dc_field(metadata=dc_config(field_name="user")) - type: str = dc_field(default="call.ring", metadata=dc_config(field_name="type")) - - -@dataclass -class CallRtmpBroadcastFailedEvent(DataClassJsonMixin): - call_cid: str = dc_field(metadata=dc_config(field_name="call_cid")) - created_at: datetime = dc_field( - metadata=dc_config( - field_name="created_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ) - ) - name: str = dc_field(metadata=dc_config(field_name="name")) - type: str = dc_field( - default="call.rtmp_broadcast_failed", metadata=dc_config(field_name="type") - ) - - -@dataclass -class CallRtmpBroadcastStartedEvent(DataClassJsonMixin): - call_cid: str = dc_field(metadata=dc_config(field_name="call_cid")) - created_at: datetime = dc_field( - metadata=dc_config( - field_name="created_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ) - ) - name: str = dc_field(metadata=dc_config(field_name="name")) - type: str = dc_field( - default="call.rtmp_broadcast_started", metadata=dc_config(field_name="type") - ) - - -@dataclass -class CallRtmpBroadcastStoppedEvent(DataClassJsonMixin): - call_cid: str = dc_field(metadata=dc_config(field_name="call_cid")) - created_at: datetime = dc_field( - metadata=dc_config( - field_name="created_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ) - ) - name: str = dc_field(metadata=dc_config(field_name="name")) - type: str = dc_field( - default="call.rtmp_broadcast_stopped", metadata=dc_config(field_name="type") - ) - - -@dataclass -class CallSession(DataClassJsonMixin): - anonymous_participant_count: int = dc_field( - metadata=dc_config(field_name="AnonymousParticipantCount") - ) - app_pk: int = dc_field(metadata=dc_config(field_name="AppPK")) - call_id: str = dc_field(metadata=dc_config(field_name="CallID")) - call_type: str = dc_field(metadata=dc_config(field_name="CallType")) - created_at: datetime = dc_field( - metadata=dc_config( - field_name="CreatedAt", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ) - ) - session_id: str = dc_field(metadata=dc_config(field_name="SessionID")) - active_sf_us: "List[SFUIDLastSeen]" = dc_field( - metadata=dc_config(field_name="ActiveSFUs") - ) - participants: "List[CallParticipant]" = dc_field( - metadata=dc_config(field_name="Participants") - ) - sfui_ds: List[str] = dc_field(metadata=dc_config(field_name="SFUIDs")) - accepted_by: "Dict[str, datetime]" = dc_field( - metadata=dc_config( - field_name="AcceptedBy", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ) - ) - missed_by: "Dict[str, datetime]" = dc_field( - metadata=dc_config( - field_name="MissedBy", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ) - ) - participants_count_by_role: "Dict[str, int]" = dc_field( - metadata=dc_config(field_name="ParticipantsCountByRole") - ) - rejected_by: "Dict[str, datetime]" = dc_field( - metadata=dc_config( - field_name="RejectedBy", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ) - ) - user_permission_overrides: "Dict[str, Dict[str, bool]]" = dc_field( - metadata=dc_config(field_name="UserPermissionOverrides") - ) - deleted_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="DeletedAt", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), + egress: "EgressResponse" = dc_field(metadata=dc_config(field_name="egress")) + ingress: "CallIngressResponse" = dc_field(metadata=dc_config(field_name="ingress")) + settings: "CallSettingsResponse" = dc_field( + metadata=dc_config(field_name="settings") + ) + channel_cid: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="channel_cid") ) ended_at: Optional[datetime] = dc_field( default=None, metadata=dc_config( - field_name="EndedAt", + field_name="ended_at", encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), ), ) - live_ended_at: Optional[datetime] = dc_field( + join_ahead_time_seconds: Optional[int] = dc_field( + default=None, metadata=dc_config(field_name="join_ahead_time_seconds") + ) + starts_at: Optional[datetime] = dc_field( default=None, metadata=dc_config( - field_name="LiveEndedAt", + field_name="starts_at", encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), ), ) - live_started_at: Optional[datetime] = dc_field( - default=None, + team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) + session: "Optional[CallSessionResponse]" = dc_field( + default=None, metadata=dc_config(field_name="session") + ) + thumbnails: "Optional[ThumbnailResponse]" = dc_field( + default=None, metadata=dc_config(field_name="thumbnails") + ) + + +@dataclass +class CallRingEvent(DataClassJsonMixin): + call_cid: str = dc_field(metadata=dc_config(field_name="call_cid")) + created_at: datetime = dc_field( metadata=dc_config( - field_name="LiveStartedAt", + field_name="created_at", encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ), + ) ) - ring_at: Optional[datetime] = dc_field( - default=None, + session_id: str = dc_field(metadata=dc_config(field_name="session_id")) + video: bool = dc_field(metadata=dc_config(field_name="video")) + members: "List[MemberResponse]" = dc_field(metadata=dc_config(field_name="members")) + call: "CallResponse" = dc_field(metadata=dc_config(field_name="call")) + user: "UserResponse" = dc_field(metadata=dc_config(field_name="user")) + type: str = dc_field(default="call.ring", metadata=dc_config(field_name="type")) + + +@dataclass +class CallRtmpBroadcastFailedEvent(DataClassJsonMixin): + call_cid: str = dc_field(metadata=dc_config(field_name="call_cid")) + created_at: datetime = dc_field( metadata=dc_config( - field_name="RingAt", + field_name="created_at", encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ), + ) ) - started_at: Optional[datetime] = dc_field( - default=None, + name: str = dc_field(metadata=dc_config(field_name="name")) + type: str = dc_field( + default="call.rtmp_broadcast_failed", metadata=dc_config(field_name="type") + ) + + +@dataclass +class CallRtmpBroadcastStartedEvent(DataClassJsonMixin): + call_cid: str = dc_field(metadata=dc_config(field_name="call_cid")) + created_at: datetime = dc_field( metadata=dc_config( - field_name="StartedAt", + field_name="created_at", encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ), + ) ) - timer_ends_at: Optional[datetime] = dc_field( - default=None, + name: str = dc_field(metadata=dc_config(field_name="name")) + type: str = dc_field( + default="call.rtmp_broadcast_started", metadata=dc_config(field_name="type") + ) + + +@dataclass +class CallRtmpBroadcastStoppedEvent(DataClassJsonMixin): + call_cid: str = dc_field(metadata=dc_config(field_name="call_cid")) + created_at: datetime = dc_field( metadata=dc_config( - field_name="TimerEndsAt", + field_name="created_at", encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ), + ) + ) + name: str = dc_field(metadata=dc_config(field_name="name")) + type: str = dc_field( + default="call.rtmp_broadcast_stopped", metadata=dc_config(field_name="type") ) @@ -5282,9 +4841,6 @@ class ChannelStateResponse(DataClassJsonMixin): active_live_locations: "Optional[List[SharedLocationResponseData]]" = dc_field( default=None, metadata=dc_config(field_name="active_live_locations") ) - deleted_messages: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="deleted_messages") - ) pending_messages: "Optional[List[PendingMessageResponse]]" = dc_field( default=None, metadata=dc_config(field_name="pending_messages") ) @@ -5338,9 +4894,6 @@ class ChannelStateResponseFields(DataClassJsonMixin): active_live_locations: "Optional[List[SharedLocationResponseData]]" = dc_field( default=None, metadata=dc_config(field_name="active_live_locations") ) - deleted_messages: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="deleted_messages") - ) pending_messages: "Optional[List[PendingMessageResponse]]" = dc_field( default=None, metadata=dc_config(field_name="pending_messages") ) @@ -6082,14 +5635,6 @@ class CommitMessageRequest(DataClassJsonMixin): pass -@dataclass -class CompositeAppSettings(DataClassJsonMixin): - json_encoded_settings: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="json_encoded_settings") - ) - url: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="url")) - - @dataclass class ConfigOverrides(DataClassJsonMixin): commands: List[str] = dc_field(metadata=dc_config(field_name="commands")) @@ -7343,35 +6888,6 @@ class EgressResponse(DataClassJsonMixin): ) -@dataclass -class EgressTaskConfig(DataClassJsonMixin): - egress_user: "Optional[EgressUser]" = dc_field( - default=None, metadata=dc_config(field_name="egress_user") - ) - frame_recording_egress_config: "Optional[FrameRecordingEgressConfig]" = dc_field( - default=None, metadata=dc_config(field_name="frame_recording_egress_config") - ) - hls_egress_config: "Optional[HLSEgressConfig]" = dc_field( - default=None, metadata=dc_config(field_name="hls_egress_config") - ) - recording_egress_config: "Optional[RecordingEgressConfig]" = dc_field( - default=None, metadata=dc_config(field_name="recording_egress_config") - ) - rtmp_egress_config: "Optional[RTMPEgressConfig]" = dc_field( - default=None, metadata=dc_config(field_name="rtmp_egress_config") - ) - stt_egress_config: "Optional[STTEgressConfig]" = dc_field( - default=None, metadata=dc_config(field_name="stt_egress_config") - ) - - -@dataclass -class EgressUser(DataClassJsonMixin): - token: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="token") - ) - - @dataclass class EndCallRequest(DataClassJsonMixin): pass @@ -7453,107 +6969,6 @@ class EnrichedReaction(DataClassJsonMixin): ) -@dataclass -class EntityCreator(DataClassJsonMixin): - ban_count: int = dc_field(metadata=dc_config(field_name="ban_count")) - banned: bool = dc_field(metadata=dc_config(field_name="banned")) - deleted_content_count: int = dc_field( - metadata=dc_config(field_name="deleted_content_count") - ) - id: str = dc_field(metadata=dc_config(field_name="id")) - online: bool = dc_field(metadata=dc_config(field_name="online")) - role: str = dc_field(metadata=dc_config(field_name="role")) - custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) - teams_role: "Dict[str, str]" = dc_field(metadata=dc_config(field_name="teams_role")) - avg_response_time: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="avg_response_time") - ) - ban_expires: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="ban_expires", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - created_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="created_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - deactivated_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="deactivated_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - deleted_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="deleted_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - invisible: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="invisible") - ) - language: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="language") - ) - last_active: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="last_active", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - last_engaged_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="last_engaged_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - revoke_tokens_issued_before: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="revoke_tokens_issued_before", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - updated_at: Optional[datetime] = dc_field( - default=None, - metadata=dc_config( - field_name="updated_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ), - ) - teams: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="teams") - ) - privacy_settings: "Optional[PrivacySettings]" = dc_field( - default=None, metadata=dc_config(field_name="privacy_settings") - ) - - @dataclass class EntityCreatorResponse(DataClassJsonMixin): ban_count: int = dc_field(metadata=dc_config(field_name="ban_count")) @@ -7840,47 +7255,6 @@ class ExportUsersResponse(DataClassJsonMixin): task_id: str = dc_field(metadata=dc_config(field_name="task_id")) -@dataclass -class ExternalStorage(DataClassJsonMixin): - abs_account_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="abs_account_name") - ) - abs_client_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="abs_client_id") - ) - abs_client_secret: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="abs_client_secret") - ) - abs_tenant_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="abs_tenant_id") - ) - bucket: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="bucket") - ) - gcs_credentials: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="gcs_credentials") - ) - path: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="path")) - s3_api_key: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="s3_api_key") - ) - s3_custom_endpoint: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="s3_custom_endpoint") - ) - s3_region: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="s3_region") - ) - s3_secret_key: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="s3_secret_key") - ) - storage_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="storage_name") - ) - storage_type: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="storage_type") - ) - - @dataclass class ExternalStorageResponse(DataClassJsonMixin): bucket: str = dc_field(metadata=dc_config(field_name="bucket")) @@ -8665,46 +8039,64 @@ class Flag(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) - entity_id: str = dc_field(metadata=dc_config(field_name="entity_id")) - entity_type: str = dc_field(metadata=dc_config(field_name="entity_type")) - updated_at: datetime = dc_field( + created_by_automod: bool = dc_field( + metadata=dc_config(field_name="created_by_automod") + ) + updated_at: datetime = dc_field( + metadata=dc_config( + field_name="updated_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ) + ) + approved_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="approved_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + reason: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="reason") + ) + rejected_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="rejected_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), + ) + reviewed_at: Optional[datetime] = dc_field( + default=None, metadata=dc_config( - field_name="updated_at", + field_name="reviewed_at", encoder=encode_datetime, decoder=datetime_from_unix_ns, mm_field=fields.DateTime(format="iso"), - ) - ) - result: "List[Dict[str, object]]" = dc_field( - metadata=dc_config(field_name="result") - ) - entity_creator_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="entity_creator_id") - ) - is_streamed_content: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="is_streamed_content") - ) - moderation_payload_hash: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="moderation_payload_hash") - ) - reason: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="reason") + ), ) - review_queue_item_id: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="review_queue_item_id") + reviewed_by: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="reviewed_by") ) - type: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="type")) - labels: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="labels") + target_message_id: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="target_message_id") ) custom: Optional[Dict[str, object]] = dc_field( default=None, metadata=dc_config(field_name="custom") ) - moderation_payload: "Optional[ModerationPayload]" = dc_field( - default=None, metadata=dc_config(field_name="moderation_payload") + details: "Optional[FlagDetails]" = dc_field( + default=None, metadata=dc_config(field_name="details") ) - review_queue_item: "Optional[ReviewQueueItem]" = dc_field( - default=None, metadata=dc_config(field_name="review_queue_item") + target_message: "Optional[Message]" = dc_field( + default=None, metadata=dc_config(field_name="target_message") + ) + target_user: "Optional[User]" = dc_field( + default=None, metadata=dc_config(field_name="target_user") ) user: "Optional[User]" = dc_field( default=None, metadata=dc_config(field_name="user") @@ -9001,22 +8393,6 @@ class FrameRecordSettings(DataClassJsonMixin): ) -@dataclass -class FrameRecordingEgressConfig(DataClassJsonMixin): - capture_interval_in_seconds: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="capture_interval_in_seconds") - ) - storage_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="storage_name") - ) - external_storage: "Optional[ExternalStorage]" = dc_field( - default=None, metadata=dc_config(field_name="external_storage") - ) - quality: "Optional[Quality]" = dc_field( - default=None, metadata=dc_config(field_name="quality") - ) - - @dataclass class FrameRecordingResponse(DataClassJsonMixin): status: str = dc_field(metadata=dc_config(field_name="status")) @@ -9891,22 +9267,6 @@ class GroupedStatsResponse(DataClassJsonMixin): unique: int = dc_field(metadata=dc_config(field_name="unique")) -@dataclass -class HLSEgressConfig(DataClassJsonMixin): - playlist_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="playlist_url") - ) - start_unix_nano: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="start_unix_nano") - ) - qualities: "Optional[List[Quality]]" = dc_field( - default=None, metadata=dc_config(field_name="qualities") - ) - composite_app_settings: "Optional[CompositeAppSettings]" = dc_field( - default=None, metadata=dc_config(field_name="composite_app_settings") - ) - - @dataclass class HLSSettings(DataClassJsonMixin): auto_on: bool = dc_field(metadata=dc_config(field_name="auto_on")) @@ -10659,11 +10019,6 @@ class MemberAddedEvent(DataClassJsonMixin): ) -@dataclass -class MemberLookup(DataClassJsonMixin): - limit: int = dc_field(metadata=dc_config(field_name="Limit")) - - @dataclass class MemberRemovedEvent(DataClassJsonMixin): channel_id: str = dc_field(metadata=dc_config(field_name="channel_id")) @@ -11176,7 +10531,9 @@ class MessageNewEvent(DataClassJsonMixin): ) ) watcher_count: int = dc_field(metadata=dc_config(field_name="watcher_count")) - type: str = dc_field(default="message.new", metadata=dc_config(field_name="type")) + type: str = dc_field( + default="notification.thread_message_new", metadata=dc_config(field_name="type") + ) team: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="team")) thread_participants: "Optional[List[User]]" = dc_field( default=None, metadata=dc_config(field_name="thread_participants") @@ -11853,6 +11210,7 @@ class ModerationConfig(DataClassJsonMixin): @dataclass class ModerationCustomActionEvent(DataClassJsonMixin): + action_id: str = dc_field(metadata=dc_config(field_name="action_id")) created_at: datetime = dc_field( metadata=dc_config( field_name="created_at", @@ -11861,17 +11219,27 @@ class ModerationCustomActionEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + review_queue_item: "ReviewQueueItemResponse" = dc_field( + metadata=dc_config(field_name="review_queue_item") + ) type: str = dc_field( default="moderation.custom_action", metadata=dc_config(field_name="type") ) - item: "Optional[ReviewQueueItem]" = dc_field( - default=None, metadata=dc_config(field_name="item") + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), ) - message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="message") + action_options: Optional[Dict[str, object]] = dc_field( + default=None, metadata=dc_config(field_name="action_options") ) - user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") + message: "Optional[MessageResponse]" = dc_field( + default=None, metadata=dc_config(field_name="message") ) @@ -11971,18 +11339,23 @@ class ModerationMarkReviewedEvent(DataClassJsonMixin): mm_field=fields.DateTime(format="iso"), ) ) + custom: Dict[str, object] = dc_field(metadata=dc_config(field_name="custom")) + item: "ReviewQueueItemResponse" = dc_field(metadata=dc_config(field_name="item")) type: str = dc_field( default="moderation.mark_reviewed", metadata=dc_config(field_name="type") ) - item: "Optional[ReviewQueueItem]" = dc_field( - default=None, metadata=dc_config(field_name="item") + received_at: Optional[datetime] = dc_field( + default=None, + metadata=dc_config( + field_name="received_at", + encoder=encode_datetime, + decoder=datetime_from_unix_ns, + mm_field=fields.DateTime(format="iso"), + ), ) - message: "Optional[Message]" = dc_field( + message: "Optional[MessageResponse]" = dc_field( default=None, metadata=dc_config(field_name="message") ) - user: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="user") - ) @dataclass @@ -12362,11 +11735,6 @@ class NotificationTrigger(DataClassJsonMixin): type: str = dc_field(metadata=dc_config(field_name="type")) -@dataclass -class NullTime(DataClassJsonMixin): - pass - - @dataclass class OCRRule(DataClassJsonMixin): action: str = dc_field(metadata=dc_config(field_name="action")) @@ -13534,26 +12902,6 @@ class PushTemplate(DataClassJsonMixin): ) -@dataclass -class Quality(DataClassJsonMixin): - bitdepth: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="bitdepth") - ) - framerate: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="framerate") - ) - height: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="height") - ) - name: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="name")) - video_bitrate: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="video_bitrate") - ) - width: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="width") - ) - - @dataclass class QualityScoreReport(DataClassJsonMixin): histogram: "List[ReportByHistogramBucket]" = dc_field( @@ -14635,19 +13983,6 @@ class RTMPBroadcastRequest(DataClassJsonMixin): ) -@dataclass -class RTMPEgressConfig(DataClassJsonMixin): - rtmp_location: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="rtmp_location") - ) - composite_app_settings: "Optional[CompositeAppSettings]" = dc_field( - default=None, metadata=dc_config(field_name="composite_app_settings") - ) - quality: "Optional[Quality]" = dc_field( - default=None, metadata=dc_config(field_name="quality") - ) - - @dataclass class RTMPIngress(DataClassJsonMixin): address: str = dc_field(metadata=dc_config(field_name="address")) @@ -15009,28 +14344,6 @@ class RecordSettingsResponse(DataClassJsonMixin): layout: "LayoutSettingsResponse" = dc_field(metadata=dc_config(field_name="layout")) -@dataclass -class RecordingEgressConfig(DataClassJsonMixin): - audio_only: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="audio_only") - ) - storage_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="storage_name") - ) - composite_app_settings: "Optional[CompositeAppSettings]" = dc_field( - default=None, metadata=dc_config(field_name="composite_app_settings") - ) - external_storage: "Optional[ExternalStorage]" = dc_field( - default=None, metadata=dc_config(field_name="external_storage") - ) - quality: "Optional[Quality]" = dc_field( - default=None, metadata=dc_config(field_name="quality") - ) - video_orientation_hint: "Optional[VideoOrientation]" = dc_field( - default=None, metadata=dc_config(field_name="video_orientation_hint") - ) - - @dataclass class RejectFeedMemberInviteRequest(DataClassJsonMixin): user_id: Optional[str] = dc_field( @@ -15285,84 +14598,6 @@ class RestoreUsersRequest(DataClassJsonMixin): user_ids: List[str] = dc_field(metadata=dc_config(field_name="user_ids")) -@dataclass -class ReviewQueueItem(DataClassJsonMixin): - ai_text_severity: str = dc_field(metadata=dc_config(field_name="ai_text_severity")) - bounce_count: int = dc_field(metadata=dc_config(field_name="bounce_count")) - config_key: str = dc_field(metadata=dc_config(field_name="config_key")) - content_changed: bool = dc_field(metadata=dc_config(field_name="content_changed")) - created_at: datetime = dc_field( - metadata=dc_config( - field_name="created_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ) - ) - entity_id: str = dc_field(metadata=dc_config(field_name="entity_id")) - entity_type: str = dc_field(metadata=dc_config(field_name="entity_type")) - flags_count: int = dc_field(metadata=dc_config(field_name="flags_count")) - has_image: bool = dc_field(metadata=dc_config(field_name="has_image")) - has_text: bool = dc_field(metadata=dc_config(field_name="has_text")) - has_video: bool = dc_field(metadata=dc_config(field_name="has_video")) - id: str = dc_field(metadata=dc_config(field_name="id")) - moderation_payload_hash: str = dc_field( - metadata=dc_config(field_name="moderation_payload_hash") - ) - recommended_action: str = dc_field( - metadata=dc_config(field_name="recommended_action") - ) - reviewed_by: str = dc_field(metadata=dc_config(field_name="reviewed_by")) - severity: int = dc_field(metadata=dc_config(field_name="severity")) - status: str = dc_field(metadata=dc_config(field_name="status")) - updated_at: datetime = dc_field( - metadata=dc_config( - field_name="updated_at", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ) - ) - actions: "List[ActionLog]" = dc_field(metadata=dc_config(field_name="actions")) - bans: "List[Ban]" = dc_field(metadata=dc_config(field_name="bans")) - flag_labels: List[str] = dc_field(metadata=dc_config(field_name="flag_labels")) - flag_types: List[str] = dc_field(metadata=dc_config(field_name="flag_types")) - flags: "List[Flag]" = dc_field(metadata=dc_config(field_name="flags")) - languages: List[str] = dc_field(metadata=dc_config(field_name="languages")) - reporter_ids: List[str] = dc_field(metadata=dc_config(field_name="reporter_ids")) - teams: List[str] = dc_field(metadata=dc_config(field_name="teams")) - archived_at: "NullTime" = dc_field(metadata=dc_config(field_name="archived_at")) - completed_at: "NullTime" = dc_field(metadata=dc_config(field_name="completed_at")) - reviewed_at: "NullTime" = dc_field(metadata=dc_config(field_name="reviewed_at")) - activity: "Optional[EnrichedActivity]" = dc_field( - default=None, metadata=dc_config(field_name="activity") - ) - assigned_to: "Optional[User]" = dc_field( - default=None, metadata=dc_config(field_name="assigned_to") - ) - call: "Optional[Call]" = dc_field( - default=None, metadata=dc_config(field_name="call") - ) - entity_creator: "Optional[EntityCreator]" = dc_field( - default=None, metadata=dc_config(field_name="entity_creator") - ) - feeds_v2_activity: "Optional[EnrichedActivity]" = dc_field( - default=None, metadata=dc_config(field_name="feeds_v2_activity") - ) - feeds_v2_reaction: "Optional[Reaction]" = dc_field( - default=None, metadata=dc_config(field_name="feeds_v2_reaction") - ) - message: "Optional[Message]" = dc_field( - default=None, metadata=dc_config(field_name="message") - ) - moderation_payload: "Optional[ModerationPayload]" = dc_field( - default=None, metadata=dc_config(field_name="moderation_payload") - ) - reaction: "Optional[Reaction]" = dc_field( - default=None, metadata=dc_config(field_name="reaction") - ) - - @dataclass class ReviewQueueItemNewEvent(DataClassJsonMixin): created_at: datetime = dc_field( @@ -15588,7 +14823,7 @@ class Role(DataClassJsonMixin): @dataclass class RuleBuilderAction(DataClassJsonMixin): - type: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="type")) + type: str = dc_field(metadata=dc_config(field_name="type")) ban_options: "Optional[BanOptions]" = dc_field( default=None, metadata=dc_config(field_name="ban_options") ) @@ -15702,58 +14937,11 @@ class SDKUsageReportResponse(DataClassJsonMixin): ) -@dataclass -class SFUIDLastSeen(DataClassJsonMixin): - id: str = dc_field(metadata=dc_config(field_name="id")) - last_seen: datetime = dc_field( - metadata=dc_config( - field_name="last_seen", - encoder=encode_datetime, - decoder=datetime_from_unix_ns, - mm_field=fields.DateTime(format="iso"), - ) - ) - process_start_time: int = dc_field( - metadata=dc_config(field_name="process_start_time") - ) - - @dataclass class SRTIngress(DataClassJsonMixin): address: str = dc_field(metadata=dc_config(field_name="address")) -@dataclass -class STTEgressConfig(DataClassJsonMixin): - closed_captions_enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="closed_captions_enabled") - ) - language: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="language") - ) - storage_name: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="storage_name") - ) - translations_enabled: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="translations_enabled") - ) - upload_transcriptions: Optional[bool] = dc_field( - default=None, metadata=dc_config(field_name="upload_transcriptions") - ) - whisper_server_base_url: Optional[str] = dc_field( - default=None, metadata=dc_config(field_name="whisper_server_base_url") - ) - translation_languages: Optional[List[str]] = dc_field( - default=None, metadata=dc_config(field_name="translation_languages") - ) - external_storage: "Optional[ExternalStorage]" = dc_field( - default=None, metadata=dc_config(field_name="external_storage") - ) - speech_segment_config: "Optional[SpeechSegmentConfig]" = dc_field( - default=None, metadata=dc_config(field_name="speech_segment_config") - ) - - @dataclass class ScreensharingSettings(DataClassJsonMixin): access_request_enabled: bool = dc_field( @@ -18620,6 +17808,52 @@ class UpdatedCallPermissionsEvent(DataClassJsonMixin): ) +@dataclass +class UploadChannelFileRequest(DataClassJsonMixin): + file: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="file")) + user: "Optional[OnlyUserID]" = dc_field( + default=None, metadata=dc_config(field_name="user") + ) + + +@dataclass +class UploadChannelFileResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + file: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="file")) + moderation_action: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="moderation_action") + ) + thumb_url: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="thumb_url") + ) + + +@dataclass +class UploadChannelRequest(DataClassJsonMixin): + file: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="file")) + upload_sizes: "Optional[List[ImageSize]]" = dc_field( + default=None, metadata=dc_config(field_name="upload_sizes") + ) + user: "Optional[OnlyUserID]" = dc_field( + default=None, metadata=dc_config(field_name="user") + ) + + +@dataclass +class UploadChannelResponse(DataClassJsonMixin): + duration: str = dc_field(metadata=dc_config(field_name="duration")) + file: Optional[str] = dc_field(default=None, metadata=dc_config(field_name="file")) + moderation_action: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="moderation_action") + ) + thumb_url: Optional[str] = dc_field( + default=None, metadata=dc_config(field_name="thumb_url") + ) + upload_sizes: "Optional[List[ImageSize]]" = dc_field( + default=None, metadata=dc_config(field_name="upload_sizes") + ) + + @dataclass class UpsertActivitiesRequest(DataClassJsonMixin): activities: "List[ActivityRequest]" = dc_field( @@ -19722,13 +18956,6 @@ class VideoKickUserRequest(DataClassJsonMixin): pass -@dataclass -class VideoOrientation(DataClassJsonMixin): - orientation: Optional[int] = dc_field( - default=None, metadata=dc_config(field_name="orientation") - ) - - @dataclass class VideoReactionOverTimeResponse(DataClassJsonMixin): by_minute: "Optional[List[CountByMinuteResponse]]" = dc_field( diff --git a/tests/test_metrics_prometheus_manual.py b/tests/test_metrics_prometheus_manual.py index 14513b93..4557e0d8 100644 --- a/tests/test_metrics_prometheus_manual.py +++ b/tests/test_metrics_prometheus_manual.py @@ -1,11 +1,13 @@ -import os import time import uuid import pytest +from getstream import Stream +import urllib.request +from getstream.base import StreamAPIException @pytest.mark.integration -def test_metrics_prometheus_manual(): +def test_metrics_prometheus_manual(client: Stream): """ Manual metrics check with Prometheus (real API calls). @@ -46,16 +48,16 @@ def test_metrics_prometheus_manual(): from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.exporter.prometheus import PrometheusMetricReader from prometheus_client import start_http_server - except Exception: + except ImportError: pytest.skip( "Missing Prometheus exporter; install: pip install getstream[telemetry] opentelemetry-exporter-prometheus prometheus_client" ) + return # Start Prometheus exporter on :9464 resource = Resource.create( { "service.name": "getstream-metrics-manual", - "deployment.environment": os.getenv("DEPLOYMENT_ENV", "dev"), } ) reader = PrometheusMetricReader() @@ -64,12 +66,6 @@ def test_metrics_prometheus_manual(): ) start_http_server(port=9464) - # Create real Stream client (reads STREAM_* from env if not provided) - from getstream import Stream - from getstream.base import StreamAPIException - - client = Stream() # relies on env vars via pydantic Settings - # Make a few calls — some will succeed, some fail — to populate metrics # Success (read-only) try: @@ -82,6 +78,25 @@ def test_metrics_prometheus_manual(): except StreamAPIException: pass - print("Prometheus metrics available at: http://localhost:9464/metrics") - # Keep process alive briefly so you can curl/visit the endpoint during test run - time.sleep(20) + # Fetch metrics via HTTP and assert our metric names are present + + metrics_url = "http://localhost:9464/metrics" + print("Prometheus metrics available at:", metrics_url) + + def _get_metrics_text() -> str: + with urllib.request.urlopen(metrics_url, timeout=5) as resp: + return resp.read().decode("utf-8", errors="ignore") + + found = False + deadline = time.time() + 10 + while time.time() < deadline and not found: + text = _get_metrics_text() + if ( + "getstream_client_request_count_total" in text + or "getstream_client_request_duration_count" in text + ): + found = True + break + time.sleep(0.5) + + assert found, "Expected getstream metrics not found in Prometheus /metrics output" From 4a287369419143420d631760011bbeca19b79ab9 Mon Sep 17 00:00:00 2001 From: Tommaso Barbugli Date: Sat, 27 Sep 2025 18:17:34 +0200 Subject: [PATCH 07/12] wip --- tests/test_tracing_jaeger_manual.py | 2 +- tests/test_tracing_logical_names.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/test_tracing_jaeger_manual.py b/tests/test_tracing_jaeger_manual.py index 138be2a4..423b4174 100644 --- a/tests/test_tracing_jaeger_manual.py +++ b/tests/test_tracing_jaeger_manual.py @@ -2,7 +2,7 @@ import uuid import pytest from getstream import Stream -from models import ChannelInput, CallRequest +from getstream.models import ChannelInput, CallRequest @pytest.mark.integration diff --git a/tests/test_tracing_logical_names.py b/tests/test_tracing_logical_names.py index 2d6b046d..87ae82ee 100644 --- a/tests/test_tracing_logical_names.py +++ b/tests/test_tracing_logical_names.py @@ -39,6 +39,9 @@ def handler(request: httpx.Request) -> httpx.Response: spans = exporter.get_finished_spans() assert spans, "no spans captured" s = spans[-1] - assert s.name == "channel.get_or_create" - assert s.attributes.get("stream.endpoint") == "channel.get_or_create" + assert s.name == "getstream.api.chat.get_or_create_channel" + assert ( + s.attributes.get("stream.endpoint") + == "getstream.api.chat.get_or_create_channel" + ) assert s.attributes.get("stream.channel_cid") == "messaging:123" From fcbb97af87103e2ed73cfcd295cea83b14460598 Mon Sep 17 00:00:00 2001 From: Tommaso Barbugli Date: Sat, 27 Sep 2025 18:23:09 +0200 Subject: [PATCH 08/12] wip --- tests/test_video_examples.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_video_examples.py b/tests/test_video_examples.py index 113212c1..1a5e767a 100644 --- a/tests/test_video_examples.py +++ b/tests/test_video_examples.py @@ -587,9 +587,7 @@ def handler(request: httpx.Request) -> httpx.Response: assert isinstance(endpoint_attr, str) and endpoint_attr.endswith("ping") assert s.attributes.get("http.request.method") == "GET" assert s.attributes.get("http.response.status_code") == 200 - # API key is redacted - assert s.attributes.get("stream.api_key").startswith("test_k") - assert s.attributes.get("stream.api_key").endswith("***") + assert s.attributes.get("stream.api_key") == "test_key_abcdefg" # Validate metrics contain our endpoint attribute md = metric_reader.get_metrics_data() From 5692cecfc8e93ee7266b303d54c19c084ac7e5bc Mon Sep 17 00:00:00 2001 From: Tommaso Barbugli Date: Sat, 27 Sep 2025 20:08:22 +0200 Subject: [PATCH 09/12] wip --- tests/test_operation_name.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_operation_name.py b/tests/test_operation_name.py index db343e9d..b4c7f4db 100644 --- a/tests/test_operation_name.py +++ b/tests/test_operation_name.py @@ -15,7 +15,7 @@ def test_operation_name_decorator_sets_span_name(): trace.set_tracer_provider(tp) class Dummy(BaseClient): - @operation_name("dummy.op") + @operation_name("common.dummy_op") def do(self): return self.get("/ping") @@ -28,4 +28,4 @@ def handler(request: httpx.Request) -> httpx.Response: c.do() spans = exporter.get_finished_spans() - assert spans and spans[-1].name == "dummy.op" + assert spans and spans[-1].name == "common.dummy_op" From 713e107a7f695073353cd9b08ddfe91251154588 Mon Sep 17 00:00:00 2001 From: Tommaso Barbugli Date: Sat, 27 Sep 2025 20:21:10 +0200 Subject: [PATCH 10/12] wip --- tests/test_video_examples.py | 18 +++++++++++++----- tests/test_video_integration.py | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/tests/test_video_examples.py b/tests/test_video_examples.py index 1a5e767a..1223dd30 100644 --- a/tests/test_video_examples.py +++ b/tests/test_video_examples.py @@ -53,7 +53,7 @@ def test_create_call_with_members(client: Stream): MemberRequest, ) - call = client.video.call("default", uuid.uuid4()) + call = client.video.call("default", str(uuid.uuid4())) call.get_or_create( data=CallRequest( created_by_id="tommaso-id", @@ -243,7 +243,7 @@ def test_create_call_with_backstage_and_join_ahead_set(client: Stream, call: Cal starts_at = datetime.now(timezone.utc) + timedelta(minutes=30) # create a call and set backstage and join ahead time to 5 minutes - call = client.video.call("livestream", uuid.uuid4()) + call = client.video.call("livestream", str(uuid.uuid4())) response = call.get_or_create( data=CallRequest( starts_at=starts_at, @@ -300,6 +300,14 @@ def test_create_call_with_custom_session_inactivity_timeout(call: Call): @pytest.mark.skip_in_ci def test_create_call_type_with_custom_session_inactivity_timeout(client: Stream): + call_types = [c for c in client.get_app().data.app.call_types.values()] + if len(call_types) > 20: + for c in call_types: + try: + client.video.delete_call_type(c.name) + except StreamAPIException: + pass + # create a call type with a session inactivity timeout of 5 minutes response = client.video.create_call_type( name="long_inactivity_timeout_" + str(uuid.uuid4()), @@ -317,7 +325,7 @@ def test_start_stop_frame_recording(client: Stream): user_id = str(uuid.uuid4()) # create a call and set its frame recording settings - call = client.video.call("default", uuid.uuid4()) + call = client.video.call("default", str(uuid.uuid4())) call.get_or_create(data=CallRequest(created_by_id=user_id)) with pytest.raises(StreamAPIException) as e_info: @@ -363,7 +371,7 @@ def test_create_call_with_custom_frame_recording_settings(client: Stream): user_id = str(uuid.uuid4()) # create a call and set its frame recording settings - call = client.video.call("default", uuid.uuid4()) + call = client.video.call("default", str(uuid.uuid4())) response = call.get_or_create( data=CallRequest( created_by_id=user_id, @@ -526,7 +534,7 @@ async def test_srt(async_client: AsyncStream): assert call.create_srt_credentials(user_id).address != "" -def test_otel_tracing_and_metrics_baseclient(): +def test_otel_tracing_and_metrics_base_client(): """Verify BaseClient emits OTel spans and metrics with attributes.""" from opentelemetry import trace, metrics from opentelemetry.sdk.trace import TracerProvider diff --git a/tests/test_video_integration.py b/tests/test_video_integration.py index b7b45efe..f21aee53 100644 --- a/tests/test_video_integration.py +++ b/tests/test_video_integration.py @@ -300,7 +300,7 @@ def test_custom_recording_website(self, client: Stream): @pytest.mark.skip_in_ci def test_delete_call_type(self, client: Stream): try: - response = client.video.delete_call_type(name=CALL_TYPE_NAME) + client.video.delete_call_type(name=CALL_TYPE_NAME) except Exception: time.sleep(2) response = client.video.delete_call_type(name=CALL_TYPE_NAME) From 3ce89d8d39e7532d7263592d8efca043f20bad2c Mon Sep 17 00:00:00 2001 From: Tommaso Barbugli Date: Sat, 27 Sep 2025 21:01:29 +0200 Subject: [PATCH 11/12] wip --- tests/test_operation_name.py | 10 +++++--- tests/test_tracing_logical_names.py | 2 +- tests/test_video_examples.py | 39 ++++++++--------------------- 3 files changed, 19 insertions(+), 32 deletions(-) diff --git a/tests/test_operation_name.py b/tests/test_operation_name.py index b4c7f4db..1456cf64 100644 --- a/tests/test_operation_name.py +++ b/tests/test_operation_name.py @@ -10,9 +10,13 @@ def test_operation_name_decorator_sets_span_name(): import httpx exporter = InMemorySpanExporter() - tp = TracerProvider() - tp.add_span_processor(SimpleSpanProcessor(exporter)) - trace.set_tracer_provider(tp) + provider = trace.get_tracer_provider() + if hasattr(provider, "add_span_processor"): + provider.add_span_processor(SimpleSpanProcessor(exporter)) + else: + tp = TracerProvider() + tp.add_span_processor(SimpleSpanProcessor(exporter)) + trace.set_tracer_provider(tp) class Dummy(BaseClient): @operation_name("common.dummy_op") diff --git a/tests/test_tracing_logical_names.py b/tests/test_tracing_logical_names.py index 87ae82ee..47ddf430 100644 --- a/tests/test_tracing_logical_names.py +++ b/tests/test_tracing_logical_names.py @@ -8,7 +8,7 @@ def _setup_tracer(): exporter = InMemorySpanExporter() provider = trace.get_tracer_provider() - if isinstance(provider, TracerProvider): + if hasattr(provider, "add_span_processor"): provider.add_span_processor(SimpleSpanProcessor(exporter)) else: tp = TracerProvider() diff --git a/tests/test_video_examples.py b/tests/test_video_examples.py index 1223dd30..2edfa430 100644 --- a/tests/test_video_examples.py +++ b/tests/test_video_examples.py @@ -548,7 +548,7 @@ def test_otel_tracing_and_metrics_base_client(): # Configure in-memory exporters; avoid overriding if already set span_exporter = InMemorySpanExporter() provider = trace.get_tracer_provider() - if isinstance(provider, TracerProvider): + if hasattr(provider, "add_span_processor"): provider.add_span_processor(SimpleSpanProcessor(span_exporter)) else: tp = TracerProvider() @@ -590,34 +590,18 @@ def handler(request: httpx.Request) -> httpx.Response: spans = span_exporter.get_finished_spans() assert len(spans) >= 1 s = spans[-1] - # Endpoint should be the normalized path if no logical operation is set + # Endpoint should be a string identifying the operation endpoint_attr = s.attributes.get("stream.endpoint") - assert isinstance(endpoint_attr, str) and endpoint_attr.endswith("ping") + assert isinstance(endpoint_attr, str) assert s.attributes.get("http.request.method") == "GET" assert s.attributes.get("http.response.status_code") == 200 - assert s.attributes.get("stream.api_key") == "test_key_abcdefg" - - # Validate metrics contain our endpoint attribute - md = metric_reader.get_metrics_data() - names_seen = set() - endpoints = set() - for rm in md.resource_metrics: - for sm in rm.scope_metrics: - for metric in sm.metrics: - names_seen.add(metric.name) - if metric.name in ( - "getstream.client.request.duration", - "getstream.client.request.count", - ): - for dp in metric.data.data_points: # type: ignore[attr-defined] - attrs = dict(dp.attributes) # type: ignore[attr-defined] - if "stream.endpoint" in attrs: - endpoints.add(attrs["stream.endpoint"]) - assert { - "getstream.client.request.duration", - "getstream.client.request.count", - }.issubset(names_seen) - assert any(isinstance(ep, str) and ep.endswith("ping") for ep in endpoints) + api_key_attr = s.attributes.get("stream.api_key") + assert isinstance(api_key_attr, str) + assert api_key_attr == "test_key_abcdefg" or ( + api_key_attr.startswith("test_k") and api_key_attr.endswith("***") + ) + + # Metrics are validated in integration/manual tests; spans are the focus here. def test_otel_baggage_call_cid_video(monkeypatch): @@ -632,8 +616,7 @@ def test_otel_baggage_call_cid_video(monkeypatch): # Tracing only (metrics validated in previous test) span_exporter = InMemorySpanExporter() provider = trace.get_tracer_provider() - # If provider is not SDK TracerProvider yet, set it; otherwise, reuse and add processor - if isinstance(provider, TracerProvider): + if hasattr(provider, "add_span_processor"): provider.add_span_processor(SimpleSpanProcessor(span_exporter)) else: tp = TracerProvider() From 7e64a16767e120eb2e2951d7037b508f22964555 Mon Sep 17 00:00:00 2001 From: Tommaso Barbugli Date: Sat, 27 Sep 2025 21:52:54 +0200 Subject: [PATCH 12/12] cleanup --- getstream/common/telemetry.py | 75 ++++++++--------------------------- getstream/stream.py | 4 +- 2 files changed, 20 insertions(+), 59 deletions(-) diff --git a/getstream/common/telemetry.py b/getstream/common/telemetry.py index 8ffe60be..3c5f05ae 100644 --- a/getstream/common/telemetry.py +++ b/getstream/common/telemetry.py @@ -4,9 +4,13 @@ import os import time from contextlib import contextmanager -from typing import Any, Dict, Optional, Callable, Awaitable +from typing import Any, Dict, Optional, Callable, Awaitable, TYPE_CHECKING import inspect +if TYPE_CHECKING: + from getstream.chat.channel import Channel + from getstream.video import BaseCall + # Optional OpenTelemetry imports with graceful fallback try: from opentelemetry import baggage, context as otel_context, metrics, trace @@ -38,11 +42,6 @@ "authorization,password,token,secret,api_key,authorization_bearer,auth", ).split(",") } -REDACT_API_KEY = os.getenv("STREAM_OTEL_REDACT_API_KEY", "true").lower() in { - "1", - "true", - "yes", -} def _noop_cm(): # pragma: no cover - used when OTel missing @@ -205,22 +204,6 @@ def span_request( pass -@contextmanager -def with_call_context(call_cid: str): - """Attach `stream.call_cid` baggage for the scope of the context (no-op if OTel missing).""" - if not _HAS_OTEL or baggage is None or otel_context is None: # pragma: no cover - yield - return - ctx = baggage.set_baggage( - "stream.call_cid", call_cid, context=otel_context.get_current() - ) - token = otel_context.attach(ctx) - try: - yield - finally: - otel_context.detach(token) - - def current_operation(default: Optional[str] = None) -> Optional[str]: """Return current logical operation name from baggage (stream.operation).""" if not _HAS_OTEL or baggage is None: @@ -231,18 +214,12 @@ def current_operation(default: Optional[str] = None) -> Optional[str]: # Decorators for auto-attaching baggage around method calls def attach_call_cid(func: Callable[..., Any]) -> Callable[..., Any]: - def wrapper(self, *args, **kwargs): - cid = None - try: - cid = f"{getattr(self, 'call_type', None)}:{getattr(self, 'id', None)}" - except Exception: - cid = None - op = f"call.{getattr(func, '__name__', 'unknown')}" - if cid and _HAS_OTEL and baggage is not None and otel_context is not None: + def wrapper(self: BaseCall, *args, **kwargs): + cid = f"{self.call_type}:{self.id}" + if _HAS_OTEL and baggage is not None and otel_context is not None: ctx = baggage.set_baggage( "stream.call_cid", cid, context=otel_context.get_current() ) - ctx = baggage.set_baggage("stream.operation", op, context=ctx) token = otel_context.attach(ctx) try: return func(self, *args, **kwargs) @@ -256,18 +233,12 @@ def wrapper(self, *args, **kwargs): def attach_call_cid_async( func: Callable[..., Awaitable[Any]], ) -> Callable[..., Awaitable[Any]]: - async def wrapper(self, *args, **kwargs): - cid = None - try: - cid = f"{getattr(self, 'call_type', None)}:{getattr(self, 'id', None)}" - except Exception: - cid = None - op = f"call.{getattr(func, '__name__', 'unknown')}" - if cid and _HAS_OTEL and baggage is not None and otel_context is not None: + async def wrapper(self: BaseCall, *args, **kwargs): + cid = f"{self.call_type}:{self.id}" + if _HAS_OTEL and baggage is not None and otel_context is not None: ctx = baggage.set_baggage( "stream.call_cid", cid, context=otel_context.get_current() ) - ctx = baggage.set_baggage("stream.operation", op, context=ctx) token = otel_context.attach(ctx) try: return await func(self, *args, **kwargs) @@ -279,19 +250,13 @@ async def wrapper(self, *args, **kwargs): def attach_channel_cid(func: Callable[..., Any]) -> Callable[..., Any]: - def wrapper(self, *args, **kwargs): - cid = None - try: - cid = f"{getattr(self, 'channel_type', None)}:{getattr(self, 'channel_id', None)}" - except Exception: - cid = None - op = f"channel.{getattr(func, '__name__', 'unknown')}" - if cid and _HAS_OTEL and baggage is not None and otel_context is not None: + def wrapper(self: Channel, *args, **kwargs): + cid = f"{self.channel_type}:{self.channel_id}" + if _HAS_OTEL and baggage is not None and otel_context is not None: # Attach channel_cid to baggage under a different key than call_cid ctx = baggage.set_baggage( "stream.channel_cid", cid, context=otel_context.get_current() ) - ctx = baggage.set_baggage("stream.operation", op, context=ctx) token = otel_context.attach(ctx) try: return func(self, *args, **kwargs) @@ -305,18 +270,12 @@ def wrapper(self, *args, **kwargs): def attach_channel_cid_async( func: Callable[..., Awaitable[Any]], ) -> Callable[..., Awaitable[Any]]: - async def wrapper(self, *args, **kwargs): - cid = None - try: - cid = f"{getattr(self, 'channel_type', None)}:{getattr(self, 'channel_id', None)}" - except Exception: - cid = None - op = f"channel.{getattr(func, '__name__', 'unknown')}" - if cid and _HAS_OTEL and baggage is not None and otel_context is not None: + async def wrapper(self: Channel, *args, **kwargs): + cid = f"{self.channel_type}:{self.channel_id}" + if _HAS_OTEL and baggage is not None and otel_context is not None: ctx = baggage.set_baggage( "stream.channel_cid", cid, context=otel_context.get_current() ) - ctx = baggage.set_baggage("stream.operation", op, context=ctx) token = otel_context.attach(ctx) try: return await func(self, *args, **kwargs) diff --git a/getstream/stream.py b/getstream/stream.py index 96a8de64..edacf4a1 100644 --- a/getstream/stream.py +++ b/getstream/stream.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from functools import cached_property import time from typing import List, Optional @@ -227,7 +229,7 @@ class Stream(BaseStream, CommonClient): @classmethod @deprecated("from_env is deprecated, use __init__ instead") - def from_env(cls, timeout: float = 6.0) -> "Stream": + def from_env(cls, timeout: float = 6.0) -> Stream: """ Construct a StreamClient by loading its credentials and base_url from environment variables (via our pydantic Settings).