beamlit 0.0.51__py3-none-any.whl → 0.0.53__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. beamlit/agents/chain.py +50 -3
  2. beamlit/agents/chat.py +92 -7
  3. beamlit/agents/decorator.py +43 -9
  4. beamlit/agents/thread.py +14 -0
  5. beamlit/agents/voice/openai.py +5 -7
  6. beamlit/authentication/apikey.py +30 -0
  7. beamlit/authentication/authentication.py +64 -0
  8. beamlit/authentication/clientcredentials.py +51 -1
  9. beamlit/authentication/credentials.py +117 -0
  10. beamlit/authentication/device_mode.py +78 -0
  11. beamlit/common/error.py +18 -0
  12. beamlit/common/instrumentation.py +38 -12
  13. beamlit/common/logger.py +29 -0
  14. beamlit/common/secrets.py +28 -0
  15. beamlit/common/settings.py +39 -1
  16. beamlit/common/slugify.py +16 -0
  17. beamlit/common/utils.py +19 -0
  18. beamlit/deploy/__init__.py +5 -0
  19. beamlit/deploy/deploy.py +31 -15
  20. beamlit/deploy/format.py +39 -8
  21. beamlit/deploy/parser.py +16 -0
  22. beamlit/functions/__init__.py +2 -1
  23. beamlit/functions/common.py +57 -8
  24. beamlit/functions/decorator.py +21 -2
  25. beamlit/functions/mcp/mcp.py +15 -2
  26. beamlit/functions/remote/remote.py +29 -4
  27. beamlit/run.py +45 -0
  28. beamlit/serve/app.py +23 -5
  29. beamlit/serve/middlewares/__init__.py +6 -0
  30. beamlit/serve/middlewares/accesslog.py +16 -0
  31. beamlit/serve/middlewares/processtime.py +16 -0
  32. {beamlit-0.0.51.dist-info → beamlit-0.0.53.dist-info}/METADATA +1 -1
  33. {beamlit-0.0.51.dist-info → beamlit-0.0.53.dist-info}/RECORD +36 -35
  34. beamlit-0.0.53.dist-info/entry_points.txt +2 -0
  35. {beamlit-0.0.51.dist-info → beamlit-0.0.53.dist-info}/WHEEL +0 -0
  36. {beamlit-0.0.51.dist-info → beamlit-0.0.53.dist-info}/licenses/LICENSE +0 -0
beamlit/agents/chain.py CHANGED
@@ -16,7 +16,7 @@ from beamlit.run import RunClient
16
16
 
17
17
  class ChainTool(BaseTool):
18
18
  """
19
- Chain tool
19
+ A tool that allows chaining of agent actions. Extends LangChain's BaseTool.
20
20
  """
21
21
 
22
22
  client: RunClient
@@ -24,6 +24,16 @@ class ChainTool(BaseTool):
24
24
 
25
25
  @t.override
26
26
  def _run(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
27
+ """
28
+ Executes the tool synchronously.
29
+
30
+ Parameters:
31
+ *args (Any): Positional arguments.
32
+ **kwargs (Any): Keyword arguments.
33
+
34
+ Returns:
35
+ Any: The result of the tool execution.
36
+ """
27
37
  warnings.warn(
28
38
  "Invoke this tool asynchronousely using `ainvoke`. This method exists only to satisfy standard tests.",
29
39
  stacklevel=1,
@@ -32,6 +42,16 @@ class ChainTool(BaseTool):
32
42
 
33
43
  @t.override
34
44
  async def _arun(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
45
+ """
46
+ Executes the tool asynchronously.
47
+
48
+ Parameters:
49
+ *args (Any): Positional arguments.
50
+ **kwargs (Any): Keyword arguments.
51
+
52
+ Returns:
53
+ Any: The result of the asynchronous tool execution.
54
+ """
35
55
  settings = get_settings()
36
56
  result = self.client.run(
37
57
  "agent",
@@ -45,17 +65,30 @@ class ChainTool(BaseTool):
45
65
  @t.override
46
66
  @property
47
67
  def tool_call_schema(self) -> type[pydantic.BaseModel]:
68
+ """
69
+ Defines the schema for tool calls based on the provided argument schema.
70
+
71
+ Returns:
72
+ type[pydantic.BaseModel]: The Pydantic model representing the tool call schema.
73
+ """
48
74
  assert self.args_schema is not None # noqa: S101
49
75
  return self.args_schema
50
76
 
77
+
51
78
  class ChainInput(pydantic.BaseModel):
79
+ """
80
+ A Pydantic model representing the input structure for a chain.
81
+ """
82
+
52
83
  inputs: str
53
84
 
85
+
54
86
  @dataclass
55
87
  class ChainToolkit:
56
88
  """
57
- Remote toolkit
89
+ A toolkit for managing and initializing a chain of agents.
58
90
  """
91
+
59
92
  client: AuthenticatedClient
60
93
  chain: list[AgentChain]
61
94
  _chain: list[Agent] | None = None
@@ -63,6 +96,12 @@ class ChainToolkit:
63
96
  model_config = pydantic.ConfigDict(arbitrary_types_allowed=True)
64
97
 
65
98
  def initialize(self) -> None:
99
+ """
100
+ Initializes the toolkit by retrieving and configuring the list of agents based on the provided chains.
101
+
102
+ Raises:
103
+ RuntimeError: If initialization fails due to missing agents.
104
+ """
66
105
  """Initialize the session and retrieve tools list"""
67
106
  if self._chain is None:
68
107
  agents = list_agents.sync_detailed(
@@ -77,8 +116,16 @@ class ChainToolkit:
77
116
  agents_chain.append(agent[0])
78
117
  self._chain = agents_chain
79
118
 
80
- @t.override
81
119
  def get_tools(self) -> list[BaseTool]:
120
+ """
121
+ Retrieves a list of tools corresponding to the initialized agents.
122
+
123
+ Returns:
124
+ list[BaseTool]: A list of initialized `ChainTool` instances.
125
+
126
+ Raises:
127
+ RuntimeError: If the toolkit has not been initialized.
128
+ """
82
129
  if self._chain is None:
83
130
  raise RuntimeError("Must initialize the toolkit first")
84
131
 
beamlit/agents/chat.py CHANGED
@@ -7,39 +7,96 @@ from beamlit.api.models import get_model
7
7
  from beamlit.authentication import get_authentication_headers, new_client
8
8
  from beamlit.common.settings import get_settings
9
9
  from beamlit.models import Model
10
+
10
11
  from .voice.openai import OpenAIVoiceReactAgent
11
12
 
12
13
  logger = getLogger(__name__)
13
14
 
14
15
 
15
- def get_base_url(name: str):
16
+ def get_base_url(name: str) -> str:
17
+ """
18
+ Constructs the base URL for a given model name based on the current settings.
19
+
20
+ Parameters:
21
+ name (str): The name of the model.
22
+
23
+ Returns:
24
+ str: The constructed base URL.
25
+ """
16
26
  settings = get_settings()
17
27
  return f"{settings.run_url}/{settings.workspace}/models/{name}/v1"
18
28
 
19
29
 
20
- def get_mistral_chat_model(**kwargs):
30
+ def get_mistral_chat_model(**kwargs) -> BaseChatModel:
31
+ """
32
+ Initializes and returns a MistralAI chat model with the provided keyword arguments.
33
+
34
+ Parameters:
35
+ **kwargs: Arbitrary keyword arguments for configuring the `ChatMistralAI` model.
36
+
37
+ Returns:
38
+ ChatMistralAI: An instance of the MistralAI chat model.
39
+ """
21
40
  from langchain_mistralai.chat_models import ChatMistralAI # type: ignore
22
41
 
23
42
  return ChatMistralAI(**kwargs)
24
43
 
25
44
 
26
- def get_openai_chat_model(**kwargs):
45
+ def get_openai_chat_model(**kwargs) -> BaseChatModel:
46
+ """
47
+ Initializes and returns an OpenAI chat model with the provided keyword arguments.
48
+
49
+ Parameters:
50
+ **kwargs: Arbitrary keyword arguments for configuring the `ChatOpenAI` model.
51
+
52
+ Returns:
53
+ ChatOpenAI: An instance of the OpenAI chat model.
54
+ """
27
55
  from langchain_openai import ChatOpenAI # type: ignore
28
56
 
29
57
  return ChatOpenAI(**kwargs)
30
58
 
31
59
 
32
- def get_anthropic_chat_model(**kwargs):
60
+ def get_anthropic_chat_model(**kwargs) -> BaseChatModel:
61
+ """
62
+ Initializes and returns an Anthropic chat model with the provided keyword arguments.
63
+
64
+ Parameters:
65
+ **kwargs: Arbitrary keyword arguments for configuring the `ChatAnthropic` model.
66
+
67
+ Returns:
68
+ ChatAnthropic: An instance of the Anthropic chat model.
69
+ """
33
70
  from langchain_anthropic import ChatAnthropic # type: ignore
34
71
 
35
72
  return ChatAnthropic(**kwargs)
36
73
 
37
- def get_xai_chat_model(**kwargs):
74
+
75
+ def get_xai_chat_model(**kwargs) -> BaseChatModel:
76
+ """
77
+ Initializes and returns an XAI chat model with the provided keyword arguments.
78
+
79
+ Parameters:
80
+ **kwargs: Arbitrary keyword arguments for configuring the `ChatXAI` model.
81
+
82
+ Returns:
83
+ ChatXAI: An instance of the XAI chat model.
84
+ """
38
85
  from langchain_xai import ChatXAI # type: ignore
39
86
 
40
87
  return ChatXAI(**kwargs)
41
88
 
42
- def get_cohere_chat_model(**kwargs):
89
+
90
+ def get_cohere_chat_model(**kwargs) -> BaseChatModel:
91
+ """
92
+ Initializes and returns a Cohere chat model with the provided keyword arguments.
93
+
94
+ Parameters:
95
+ **kwargs: Arbitrary keyword arguments for configuring the `ChatCohere` model.
96
+
97
+ Returns:
98
+ ChatCohere: An instance of the Cohere chat model.
99
+ """
43
100
  from langchain_cohere import ChatCohere # type: ignore
44
101
 
45
102
  return ChatCohere(**kwargs)
@@ -64,10 +121,35 @@ def get_azure_marketplace_chat_model(**kwargs):
64
121
  ) # It seems to use a compatible endpoint, so we can use the classic OpenAI interface
65
122
 
66
123
  def get_chat_model(name: str, agent_model: Union[Model, None] = None) -> BaseChatModel:
124
+ """
125
+ Gets a chat model instance for the specified model name.
126
+
127
+ Parameters:
128
+ name (str): The name of the model to retrieve.
129
+ agent_model (Union[Model, None], optional): A pre-fetched model instance.
130
+ If None, the model will be fetched from the API. Defaults to None.
131
+
132
+ Returns:
133
+ BaseChatModel: An instance of the appropriate chat model.
134
+ """
67
135
  [chat_model, _, __] = get_chat_model_full(name, agent_model)
68
136
  return chat_model
69
137
 
70
138
  def get_chat_model_full(name: str, agent_model: Union[Model, None] = None) -> Tuple[BaseChatModel, str, str]:
139
+ """
140
+ Gets a chat model instance along with provider and model information.
141
+
142
+ Parameters:
143
+ name (str): The name of the model to retrieve.
144
+ agent_model (Union[Model, None], optional): A pre-fetched model instance.
145
+ If None, the model will be fetched from the API. Defaults to None.
146
+
147
+ Returns:
148
+ Tuple[BaseChatModel, str, str]: A tuple containing:
149
+ - The chat model instance
150
+ - The provider name (e.g., 'openai', 'anthropic', etc.)
151
+ - The specific model name (e.g., 'gpt-4o-mini')
152
+ """
71
153
  settings = get_settings()
72
154
  client = new_client()
73
155
 
@@ -77,7 +159,10 @@ def get_chat_model_full(name: str, agent_model: Union[Model, None] = None) -> Tu
77
159
  except Exception:
78
160
  logger.warning(f"Model {name} not found, defaulting to gpt-4o-mini")
79
161
 
80
- environment = (agent_model and agent_model.metadata and agent_model.metadata.environment) or settings.environment
162
+ environment = (
163
+ (agent_model and agent_model.metadata and agent_model.metadata.environment)
164
+ or settings.environment
165
+ )
81
166
  headers = get_authentication_headers(settings)
82
167
  headers["X-Beamlit-Environment"] = environment
83
168
 
@@ -1,7 +1,13 @@
1
+ """Module: decorator
2
+
3
+ Defines decorators for agent functionalities.
4
+ """
5
+
1
6
  # Import necessary modules
2
7
  import functools
3
8
  import inspect
4
9
  from logging import getLogger
10
+ from typing import Callable
5
11
 
6
12
  from langgraph.checkpoint.memory import MemorySaver
7
13
  from langgraph.prebuilt import create_react_agent
@@ -12,8 +18,9 @@ from beamlit.common.settings import init
12
18
  from beamlit.errors import UnexpectedStatus
13
19
  from beamlit.functions import get_functions
14
20
  from beamlit.models import Agent, AgentSpec, EnvironmentMetadata
15
- from .voice.openai import OpenAIVoiceReactAgent
21
+
16
22
  from .chat import get_chat_model_full
23
+ from .voice.openai import OpenAIVoiceReactAgent
17
24
 
18
25
 
19
26
  def agent(
@@ -22,7 +29,32 @@ def agent(
22
29
  override_agent=None,
23
30
  override_functions=None,
24
31
  remote_functions=None,
25
- ):
32
+ ) -> Callable:
33
+ """
34
+ A decorator factory that configures and wraps functions to integrate with Beamlit agents.
35
+ Handles model initialization, function retrieval, and agent setup.
36
+
37
+ Parameters:
38
+ agent (Agent | dict, optional): An `Agent` instance or a dictionary containing agent metadata and specifications.
39
+ override_model (Any, optional): An optional model to override the default agent model.
40
+ override_agent (Any, optional): An optional agent instance to override the default agent.
41
+ mcp_hub (Any, optional): An optional MCP hub configuration.
42
+ remote_functions (Any, optional): An optional list of remote functions to be integrated.
43
+
44
+ Returns:
45
+ Callable: A decorator that wraps the target function, injecting agent-related configurations and dependencies.
46
+
47
+ Behavior:
48
+ - Validates and initializes the agent configuration.
49
+ - Retrieves and sets up the appropriate chat model based on the agent's specifications.
50
+ - Retrieves functions from the specified directories or remote sources.
51
+ - Wraps the target function, injecting agent, model, and function dependencies as needed.
52
+ - Logs relevant information and handles exceptions during the setup process.
53
+
54
+ Raises:
55
+ ValueError: If required configurations such as the model are missing.
56
+ Re-raises exceptions encountered during model retrieval and agent setup.
57
+ """
26
58
  logger = getLogger(__name__)
27
59
  try:
28
60
  if agent is not None and not isinstance(agent, dict):
@@ -47,6 +79,7 @@ def agent(
47
79
  param.name == "functions"
48
80
  for param in inspect.signature(func).parameters.values()
49
81
  )
82
+
50
83
  @functools.wraps(func)
51
84
  def wrapped(*args, **kwargs):
52
85
  if agent_kwargs:
@@ -111,21 +144,22 @@ def agent(
111
144
  environment=settings.environment, client=client
112
145
  )
113
146
  models = ", ".join([model.metadata.name for model in models.parsed])
114
- models_select = f"You can select one from the your models: {models}"
147
+ models_select = f"You can select one from your models: {models}"
115
148
  except Exception:
116
149
  pass
117
150
 
118
- raise ValueError(f"You must provide a model.\n"
151
+ raise ValueError(
152
+ f"You must provide a model.\n"
119
153
  f"{models_select}\n"
120
154
  f"You can create one at {settings.app_url}/{settings.workspace}/global-inference-network/models/create\n"
121
155
  "Add it to your agent spec\n"
122
156
  "agent={\n"
123
- " \"metadata\": {\n"
124
- f" \"name\": \"{agent.metadata.name}\",\n"
157
+ ' "metadata": {\n'
158
+ f' "name": "{agent.metadata.name}",\n'
125
159
  " },\n"
126
- " \"spec\": {\n"
127
- " \"model\": \"MODEL_NAME\",\n"
128
- f" \"description\": \"{agent.spec.description}\",\n"
160
+ ' "spec": {\n'
161
+ ' "model": "MODEL_NAME",\n'
162
+ f' "description": "{agent.spec.description}",\n'
129
163
  " },\n"
130
164
  "}")
131
165
  if isinstance(chat_model, OpenAIVoiceReactAgent):
beamlit/agents/thread.py CHANGED
@@ -1,9 +1,23 @@
1
+ """Module: thread
1
2
 
3
+ Defines threading capabilities for agents.
4
+ """
2
5
  import jwt
3
6
  from fastapi import Request
4
7
 
5
8
 
6
9
  def get_default_thread(request: Request) -> str:
10
+ """
11
+ Extracts the default thread identifier from an incoming HTTP request.
12
+ Prioritizes the `X-Beamlit-Sub` header and falls back to decoding the JWT
13
+ from the `Authorization` or `X-Beamlit-Authorization` headers.
14
+
15
+ Parameters:
16
+ request (Request): The incoming HTTP request object.
17
+
18
+ Returns:
19
+ str: The extracted thread identifier. Returns an empty string if no valid identifier is found.
20
+ """
7
21
  if request.headers.get("X-Beamlit-Sub"):
8
22
  return request.headers.get("X-Beamlit-Sub")
9
23
  authorization = request.headers.get("Authorization", request.headers.get("X-Beamlit-Authorization"))
@@ -1,16 +1,14 @@
1
1
  import asyncio
2
2
  import json
3
- import websockets
4
-
5
3
  from contextlib import asynccontextmanager
6
- from typing import AsyncGenerator, AsyncIterator, Any, Callable, Coroutine
7
- from .utils import amerge
4
+ from typing import Any, AsyncGenerator, AsyncIterator, Callable, Coroutine
8
5
 
9
- from langchain_core.tools import BaseTool
6
+ import websockets
10
7
  from langchain_core._api import beta
11
- from langchain_core.utils import secret_from_env
8
+ from langchain_core.tools import BaseTool
9
+ from pydantic import BaseModel, Field, PrivateAttr
12
10
 
13
- from pydantic import BaseModel, Field, SecretStr, PrivateAttr
11
+ from .utils import amerge
14
12
 
15
13
  EVENTS_TO_IGNORE = {
16
14
  "response.function_call_arguments.delta",
@@ -1,20 +1,50 @@
1
+ """
2
+ This module provides the ApiKeyProvider class, which handles API key-based authentication for Beamlit.
3
+ """
4
+
1
5
  from typing import Generator
2
6
 
3
7
  from httpx import Auth, Request, Response
4
8
 
5
9
 
6
10
  class ApiKeyProvider(Auth):
11
+ """
12
+ A provider that authenticates requests using an API key.
13
+ """
14
+
7
15
  def __init__(self, credentials, workspace_name: str):
16
+ """
17
+ Initializes the ApiKeyProvider with the given credentials and workspace name.
18
+
19
+ Parameters:
20
+ credentials: Credentials containing the API key.
21
+ workspace_name (str): The name of the workspace.
22
+ """
8
23
  self.credentials = credentials
9
24
  self.workspace_name = workspace_name
10
25
 
11
26
  def get_headers(self):
27
+ """
28
+ Retrieves the authentication headers containing the API key and workspace information.
29
+
30
+ Returns:
31
+ dict: A dictionary of headers with API key and workspace.
32
+ """
12
33
  return {
13
34
  "X-Beamlit-Api-Key": self.credentials.apiKey,
14
35
  "X-Beamlit-Workspace": self.workspace_name,
15
36
  }
16
37
 
17
38
  def auth_flow(self, request: Request) -> Generator[Request, Response, None]:
39
+ """
40
+ Authenticates the request by adding API key and workspace headers.
41
+
42
+ Parameters:
43
+ request (Request): The HTTP request to authenticate.
44
+
45
+ Yields:
46
+ Request: The authenticated request.
47
+ """
18
48
  request.headers["X-Beamlit-Api-Key"] = self.credentials.apiKey
19
49
  request.headers["X-Beamlit-Workspace"] = self.workspace_name
20
50
  yield request
@@ -1,3 +1,9 @@
1
+ """
2
+ This module provides classes and functions for handling various authentication methods,
3
+ including public access, API key authentication, client credentials, and bearer tokens.
4
+ It also includes utilities for creating authenticated clients and managing authentication headers.
5
+ """
6
+
1
7
  from dataclasses import dataclass
2
8
  from typing import Dict, Generator
3
9
 
@@ -18,18 +24,43 @@ from .device_mode import BearerToken
18
24
 
19
25
 
20
26
  class PublicProvider(Auth):
27
+ """
28
+ A provider that allows public access without any authentication.
29
+ """
30
+
21
31
  def auth_flow(self, request: Request) -> Generator[Request, Response, None]:
32
+ """
33
+ Processes the authentication flow for public access by yielding the request as-is.
34
+
35
+ Parameters:
36
+ request (Request): The HTTP request to process.
37
+
38
+ Yields:
39
+ Request: The unmodified request.
40
+ """
22
41
  yield request
23
42
 
24
43
 
25
44
  @dataclass
26
45
  class RunClientWithCredentials:
46
+ """
47
+ A dataclass that holds credentials and workspace information for initializing an authenticated client.
48
+
49
+ Attributes:
50
+ credentials (Credentials): The credentials used for authentication.
51
+ workspace (str): The name of the workspace.
52
+ api_url (str): The base API URL.
53
+ run_url (str): The run-specific URL.
54
+ """
27
55
  credentials: Credentials
28
56
  workspace: str
29
57
  api_url: str = ""
30
58
  run_url: str = ""
31
59
 
32
60
  def __post_init__(self):
61
+ """
62
+ Post-initialization to set the API and run URLs from settings.
63
+ """
33
64
  from ..common.settings import get_settings
34
65
 
35
66
  settings = get_settings()
@@ -38,6 +69,15 @@ class RunClientWithCredentials:
38
69
 
39
70
 
40
71
  def new_client_from_settings(settings: Settings):
72
+ """
73
+ Creates a new authenticated client using the provided settings.
74
+
75
+ Parameters:
76
+ settings (Settings): The settings containing authentication and workspace information.
77
+
78
+ Returns:
79
+ AuthenticatedClient: An instance of AuthenticatedClient configured with the provided settings.
80
+ """
41
81
  credentials = load_credentials_from_settings(settings)
42
82
 
43
83
  client_config = RunClientWithCredentials(
@@ -48,6 +88,12 @@ def new_client_from_settings(settings: Settings):
48
88
 
49
89
 
50
90
  def new_client():
91
+ """
92
+ Creates a new authenticated client based on the current context and settings.
93
+
94
+ Returns:
95
+ AuthenticatedClient: An instance of AuthenticatedClient configured with the current context or settings.
96
+ """
51
97
  settings = get_settings()
52
98
  context = current_context()
53
99
  if context.workspace and not settings.authentication.client.credentials:
@@ -66,6 +112,15 @@ def new_client():
66
112
 
67
113
 
68
114
  def new_client_with_credentials(config: RunClientWithCredentials):
115
+ """
116
+ Creates a new authenticated client using the provided client configuration.
117
+
118
+ Parameters:
119
+ config (RunClientWithCredentials): The client configuration containing credentials and workspace information.
120
+
121
+ Returns:
122
+ AuthenticatedClient: An instance of AuthenticatedClient configured with the provided credentials.
123
+ """
69
124
  provider: Auth = None
70
125
  if config.credentials.apiKey:
71
126
  provider = ApiKeyProvider(config.credentials, config.workspace)
@@ -80,6 +135,15 @@ def new_client_with_credentials(config: RunClientWithCredentials):
80
135
 
81
136
 
82
137
  def get_authentication_headers(settings: Settings) -> Dict[str, str]:
138
+ """
139
+ Retrieves authentication headers based on the current context and settings.
140
+
141
+ Parameters:
142
+ settings (Settings): The settings containing authentication and workspace information.
143
+
144
+ Returns:
145
+ Dict[str, str]: A dictionary of authentication headers.
146
+ """
83
147
  context = current_context()
84
148
  if context.workspace and not settings.authentication.client.credentials:
85
149
  credentials = load_credentials(context.workspace)
@@ -1,3 +1,9 @@
1
+ """
2
+ This module provides the ClientCredentials class, which handles client credentials-based
3
+ authentication for Beamlit. It manages token refreshing and authentication flows using
4
+ client credentials and refresh tokens.
5
+ """
6
+
1
7
  import base64
2
8
  import json
3
9
  from dataclasses import dataclass
@@ -19,12 +25,33 @@ class DeviceLoginFinalizeResponse:
19
25
 
20
26
 
21
27
  class ClientCredentials(Auth):
28
+ """
29
+ A provider that authenticates requests using client credentials.
30
+ """
31
+
22
32
  def __init__(self, credentials, workspace_name: str, base_url: str):
33
+ """
34
+ Initializes the ClientCredentials provider with the given credentials, workspace name, and base URL.
35
+
36
+ Parameters:
37
+ credentials: Credentials containing access and refresh tokens.
38
+ workspace_name (str): The name of the workspace.
39
+ base_url (str): The base URL for authentication.
40
+ """
23
41
  self.credentials = credentials
24
42
  self.workspace_name = workspace_name
25
43
  self.base_url = base_url
26
44
 
27
45
  def get_headers(self):
46
+ """
47
+ Retrieves the authentication headers after ensuring tokens are valid.
48
+
49
+ Returns:
50
+ dict: A dictionary of headers with Bearer token and workspace.
51
+
52
+ Raises:
53
+ Exception: If token refresh fails.
54
+ """
28
55
  err = self.refresh_if_needed()
29
56
  if err:
30
57
  raise err
@@ -35,6 +62,12 @@ class ClientCredentials(Auth):
35
62
  }
36
63
 
37
64
  def refresh_if_needed(self) -> Optional[Exception]:
65
+ """
66
+ Checks if the access token needs to be refreshed and performs the refresh if necessary.
67
+
68
+ Returns:
69
+ Optional[Exception]: An exception if refreshing fails, otherwise None.
70
+ """
38
71
  settings = get_settings()
39
72
  if self.credentials.client_credentials and not self.credentials.refresh_token:
40
73
  headers = {"Authorization": f"Basic {self.credentials.client_credentials}", "Content-Type": "application/json"}
@@ -61,10 +94,21 @@ class ClientCredentials(Auth):
61
94
  if current_time + timedelta(minutes=10) > exp_time:
62
95
  return self.do_refresh()
63
96
 
64
-
65
97
  return None
66
98
 
67
99
  def auth_flow(self, request: Request) -> Generator[Request, Response, None]:
100
+ """
101
+ Processes the authentication flow by ensuring tokens are valid and adding necessary headers.
102
+
103
+ Parameters:
104
+ request (Request): The HTTP request to authenticate.
105
+
106
+ Yields:
107
+ Request: The authenticated request.
108
+
109
+ Raises:
110
+ Exception: If token refresh fails.
111
+ """
68
112
  err = self.refresh_if_needed()
69
113
  if err:
70
114
  return err
@@ -74,6 +118,12 @@ class ClientCredentials(Auth):
74
118
  yield request
75
119
 
76
120
  def do_refresh(self) -> Optional[Exception]:
121
+ """
122
+ Performs the token refresh using the refresh token.
123
+
124
+ Returns:
125
+ Optional[Exception]: An exception if refreshing fails, otherwise None.
126
+ """
77
127
  if not self.credentials.refresh_token:
78
128
  return Exception("No refresh token to refresh")
79
129