beamlit 0.0.52__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.
- beamlit/agents/chain.py +50 -3
- beamlit/agents/chat.py +92 -7
- beamlit/agents/decorator.py +43 -9
- beamlit/agents/thread.py +14 -0
- beamlit/agents/voice/openai.py +5 -7
- beamlit/authentication/apikey.py +30 -0
- beamlit/authentication/authentication.py +64 -0
- beamlit/authentication/clientcredentials.py +51 -1
- beamlit/authentication/credentials.py +117 -0
- beamlit/authentication/device_mode.py +78 -0
- beamlit/common/error.py +18 -0
- beamlit/common/instrumentation.py +38 -12
- beamlit/common/logger.py +29 -0
- beamlit/common/secrets.py +28 -0
- beamlit/common/settings.py +39 -1
- beamlit/common/slugify.py +16 -0
- beamlit/common/utils.py +19 -0
- beamlit/deploy/__init__.py +5 -0
- beamlit/deploy/deploy.py +31 -15
- beamlit/deploy/format.py +39 -8
- beamlit/deploy/parser.py +16 -0
- beamlit/functions/__init__.py +2 -1
- beamlit/functions/common.py +57 -8
- beamlit/functions/decorator.py +21 -2
- beamlit/functions/mcp/mcp.py +15 -2
- beamlit/functions/remote/remote.py +29 -4
- beamlit/run.py +45 -0
- beamlit/serve/app.py +23 -5
- beamlit/serve/middlewares/__init__.py +6 -0
- beamlit/serve/middlewares/accesslog.py +16 -0
- beamlit/serve/middlewares/processtime.py +16 -0
- {beamlit-0.0.52.dist-info → beamlit-0.0.53.dist-info}/METADATA +1 -1
- {beamlit-0.0.52.dist-info → beamlit-0.0.53.dist-info}/RECORD +36 -35
- beamlit-0.0.53.dist-info/entry_points.txt +2 -0
- {beamlit-0.0.52.dist-info → beamlit-0.0.53.dist-info}/WHEEL +0 -0
- {beamlit-0.0.52.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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 = (
|
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
|
|
beamlit/agents/decorator.py
CHANGED
@@ -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
|
-
|
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
|
147
|
+
models_select = f"You can select one from your models: {models}"
|
115
148
|
except Exception:
|
116
149
|
pass
|
117
150
|
|
118
|
-
raise ValueError(
|
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
|
-
|
124
|
-
f
|
157
|
+
' "metadata": {\n'
|
158
|
+
f' "name": "{agent.metadata.name}",\n'
|
125
159
|
" },\n"
|
126
|
-
|
127
|
-
|
128
|
-
f
|
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"))
|
beamlit/agents/voice/openai.py
CHANGED
@@ -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,
|
7
|
-
from .utils import amerge
|
4
|
+
from typing import Any, AsyncGenerator, AsyncIterator, Callable, Coroutine
|
8
5
|
|
9
|
-
|
6
|
+
import websockets
|
10
7
|
from langchain_core._api import beta
|
11
|
-
from langchain_core.
|
8
|
+
from langchain_core.tools import BaseTool
|
9
|
+
from pydantic import BaseModel, Field, PrivateAttr
|
12
10
|
|
13
|
-
from
|
11
|
+
from .utils import amerge
|
14
12
|
|
15
13
|
EVENTS_TO_IGNORE = {
|
16
14
|
"response.function_call_arguments.delta",
|
beamlit/authentication/apikey.py
CHANGED
@@ -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
|
|