agentic-fabriq-sdk 0.1.4__py3-none-any.whl → 0.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of agentic-fabriq-sdk might be problematic. Click here for more details.
- af_cli/__init__.py +8 -0
- af_cli/commands/__init__.py +3 -0
- af_cli/commands/agents.py +238 -0
- af_cli/commands/auth.py +388 -0
- af_cli/commands/config.py +102 -0
- af_cli/commands/mcp_servers.py +83 -0
- af_cli/commands/secrets.py +109 -0
- af_cli/commands/tools.py +83 -0
- af_cli/core/__init__.py +3 -0
- af_cli/core/client.py +123 -0
- af_cli/core/config.py +200 -0
- af_cli/core/oauth.py +506 -0
- af_cli/core/output.py +180 -0
- af_cli/core/token_storage.py +263 -0
- af_cli/main.py +187 -0
- {agentic_fabriq_sdk-0.1.4.dist-info → agentic_fabriq_sdk-0.1.6.dist-info}/METADATA +40 -10
- {agentic_fabriq_sdk-0.1.4.dist-info → agentic_fabriq_sdk-0.1.6.dist-info}/RECORD +19 -3
- agentic_fabriq_sdk-0.1.6.dist-info/entry_points.txt +3 -0
- {agentic_fabriq_sdk-0.1.4.dist-info → agentic_fabriq_sdk-0.1.6.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration commands for the Agentic Fabric CLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from af_cli.core.config import get_config
|
|
8
|
+
from af_cli.core.output import error, info, print_output, success
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(help="Configuration commands")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@app.command()
|
|
14
|
+
def show(
|
|
15
|
+
format: str = typer.Option("table", "--format", "-f", help="Output format"),
|
|
16
|
+
):
|
|
17
|
+
"""Show current configuration."""
|
|
18
|
+
config = get_config()
|
|
19
|
+
|
|
20
|
+
config_data = {
|
|
21
|
+
"gateway_url": config.gateway_url,
|
|
22
|
+
"tenant_id": config.tenant_id or "Not set",
|
|
23
|
+
"authenticated": "Yes" if config.is_authenticated() else "No",
|
|
24
|
+
"config_file": config.config_file,
|
|
25
|
+
"output_format": config.output_format,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
print_output(
|
|
29
|
+
config_data,
|
|
30
|
+
format_type=format,
|
|
31
|
+
title="Configuration"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@app.command()
|
|
36
|
+
def set(
|
|
37
|
+
key: str = typer.Argument(..., help="Configuration key"),
|
|
38
|
+
value: str = typer.Argument(..., help="Configuration value"),
|
|
39
|
+
):
|
|
40
|
+
"""Set configuration value."""
|
|
41
|
+
config = get_config()
|
|
42
|
+
|
|
43
|
+
valid_keys = {
|
|
44
|
+
"gateway_url": "gateway_url",
|
|
45
|
+
"tenant_id": "tenant_id",
|
|
46
|
+
"output_format": "output_format",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if key not in valid_keys:
|
|
50
|
+
error(f"Invalid configuration key: {key}")
|
|
51
|
+
error(f"Valid keys: {', '.join(valid_keys.keys())}")
|
|
52
|
+
raise typer.Exit(1)
|
|
53
|
+
|
|
54
|
+
# Set the value
|
|
55
|
+
setattr(config, valid_keys[key], value)
|
|
56
|
+
config.save()
|
|
57
|
+
|
|
58
|
+
success(f"Configuration updated: {key} = {value}")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@app.command()
|
|
62
|
+
def get(
|
|
63
|
+
key: str = typer.Argument(..., help="Configuration key"),
|
|
64
|
+
):
|
|
65
|
+
"""Get configuration value."""
|
|
66
|
+
config = get_config()
|
|
67
|
+
|
|
68
|
+
valid_keys = {
|
|
69
|
+
"gateway_url": "gateway_url",
|
|
70
|
+
"tenant_id": "tenant_id",
|
|
71
|
+
"output_format": "output_format",
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if key not in valid_keys:
|
|
75
|
+
error(f"Invalid configuration key: {key}")
|
|
76
|
+
error(f"Valid keys: {', '.join(valid_keys.keys())}")
|
|
77
|
+
raise typer.Exit(1)
|
|
78
|
+
|
|
79
|
+
value = getattr(config, valid_keys[key])
|
|
80
|
+
info(f"{key}: {value}")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@app.command()
|
|
84
|
+
def reset():
|
|
85
|
+
"""Reset configuration to defaults."""
|
|
86
|
+
config = get_config()
|
|
87
|
+
|
|
88
|
+
if not typer.confirm("Are you sure you want to reset configuration to defaults?"):
|
|
89
|
+
info("Reset cancelled")
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
# Clear authentication
|
|
93
|
+
config.clear_auth()
|
|
94
|
+
|
|
95
|
+
# Reset to defaults
|
|
96
|
+
config.gateway_url = "http://localhost:8000"
|
|
97
|
+
config.tenant_id = None
|
|
98
|
+
config.output_format = "table"
|
|
99
|
+
|
|
100
|
+
config.save()
|
|
101
|
+
|
|
102
|
+
success("Configuration reset to defaults")
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP server management commands for the Agentic Fabric CLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from af_cli.core.client import get_client
|
|
8
|
+
from af_cli.core.output import error, info, print_output, success, warning
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(help="MCP server management commands")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@app.command()
|
|
14
|
+
def list(
|
|
15
|
+
format: str = typer.Option("table", "--format", "-f", help="Output format"),
|
|
16
|
+
):
|
|
17
|
+
"""List MCP servers."""
|
|
18
|
+
try:
|
|
19
|
+
with get_client() as client:
|
|
20
|
+
response = client.get("/api/v1/mcp-servers")
|
|
21
|
+
servers = response["servers"]
|
|
22
|
+
|
|
23
|
+
if not servers:
|
|
24
|
+
warning("No MCP servers found")
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
print_output(
|
|
28
|
+
servers,
|
|
29
|
+
format_type=format,
|
|
30
|
+
title="MCP Servers"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
except Exception as e:
|
|
34
|
+
error(f"Failed to list MCP servers: {e}")
|
|
35
|
+
raise typer.Exit(1)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@app.command()
|
|
39
|
+
def get(
|
|
40
|
+
server_id: str = typer.Argument(..., help="MCP server ID"),
|
|
41
|
+
format: str = typer.Option("table", "--format", "-f", help="Output format"),
|
|
42
|
+
):
|
|
43
|
+
"""Get MCP server details."""
|
|
44
|
+
try:
|
|
45
|
+
with get_client() as client:
|
|
46
|
+
server = client.get(f"/api/v1/mcp-servers/{server_id}")
|
|
47
|
+
|
|
48
|
+
print_output(
|
|
49
|
+
server,
|
|
50
|
+
format_type=format,
|
|
51
|
+
title=f"MCP Server {server_id}"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
except Exception as e:
|
|
55
|
+
error(f"Failed to get MCP server: {e}")
|
|
56
|
+
raise typer.Exit(1)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@app.command()
|
|
60
|
+
def create(
|
|
61
|
+
name: str = typer.Option(..., "--name", "-n", help="MCP server name"),
|
|
62
|
+
base_url: str = typer.Option(..., "--base-url", "-u", help="Base URL"),
|
|
63
|
+
auth_type: str = typer.Option("API_KEY", "--auth-type", "-a", help="Authentication type"),
|
|
64
|
+
):
|
|
65
|
+
"""Create a new MCP server."""
|
|
66
|
+
try:
|
|
67
|
+
with get_client() as client:
|
|
68
|
+
data = {
|
|
69
|
+
"name": name,
|
|
70
|
+
"base_url": base_url,
|
|
71
|
+
"auth_type": auth_type,
|
|
72
|
+
"source": "STATIC",
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
server = client.post("/api/v1/mcp-servers", data)
|
|
76
|
+
|
|
77
|
+
success(f"MCP server created: {server['id']}")
|
|
78
|
+
info(f"Name: {server['name']}")
|
|
79
|
+
info(f"Base URL: {server['base_url']}")
|
|
80
|
+
|
|
81
|
+
except Exception as e:
|
|
82
|
+
error(f"Failed to create MCP server: {e}")
|
|
83
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Secret management commands for the Agentic Fabric CLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from af_cli.core.client import get_client
|
|
8
|
+
from af_cli.core.output import error, info, print_output, success
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(help="Secret management commands")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@app.command()
|
|
14
|
+
def get(
|
|
15
|
+
path: str = typer.Argument(..., help="Secret path"),
|
|
16
|
+
format: str = typer.Option("table", "--format", "-f", help="Output format"),
|
|
17
|
+
):
|
|
18
|
+
"""Get a secret."""
|
|
19
|
+
try:
|
|
20
|
+
with get_client() as client:
|
|
21
|
+
secret = client.get(f"/api/v1/secrets/{path}")
|
|
22
|
+
|
|
23
|
+
# Don't display the actual secret value in table format
|
|
24
|
+
if format == "table":
|
|
25
|
+
display_data = {
|
|
26
|
+
"path": secret["path"],
|
|
27
|
+
"description": secret.get("description", ""),
|
|
28
|
+
"version": secret["version"],
|
|
29
|
+
"created_at": secret["created_at"],
|
|
30
|
+
"updated_at": secret["updated_at"],
|
|
31
|
+
}
|
|
32
|
+
print_output(display_data, format_type=format, title=f"Secret {path}")
|
|
33
|
+
info("Use --format=json to see the secret value")
|
|
34
|
+
else:
|
|
35
|
+
print_output(secret, format_type=format)
|
|
36
|
+
|
|
37
|
+
except Exception as e:
|
|
38
|
+
error(f"Failed to get secret: {e}")
|
|
39
|
+
raise typer.Exit(1)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@app.command()
|
|
43
|
+
def create(
|
|
44
|
+
path: str = typer.Argument(..., help="Secret path"),
|
|
45
|
+
value: str = typer.Option(..., "--value", "-v", help="Secret value"),
|
|
46
|
+
description: str = typer.Option("", "--description", "-d", help="Secret description"),
|
|
47
|
+
):
|
|
48
|
+
"""Create a new secret."""
|
|
49
|
+
try:
|
|
50
|
+
with get_client() as client:
|
|
51
|
+
data = {
|
|
52
|
+
"value": value,
|
|
53
|
+
"description": description,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
secret = client.post(f"/api/v1/secrets/{path}", data)
|
|
57
|
+
|
|
58
|
+
success(f"Secret created: {secret['path']}")
|
|
59
|
+
info(f"Version: {secret['version']}")
|
|
60
|
+
|
|
61
|
+
except Exception as e:
|
|
62
|
+
error(f"Failed to create secret: {e}")
|
|
63
|
+
raise typer.Exit(1)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@app.command()
|
|
67
|
+
def update(
|
|
68
|
+
path: str = typer.Argument(..., help="Secret path"),
|
|
69
|
+
value: str = typer.Option(..., "--value", "-v", help="Secret value"),
|
|
70
|
+
description: str = typer.Option("", "--description", "-d", help="Secret description"),
|
|
71
|
+
):
|
|
72
|
+
"""Update a secret."""
|
|
73
|
+
try:
|
|
74
|
+
with get_client() as client:
|
|
75
|
+
data = {
|
|
76
|
+
"value": value,
|
|
77
|
+
"description": description,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
secret = client.put(f"/api/v1/secrets/{path}", data)
|
|
81
|
+
|
|
82
|
+
success(f"Secret updated: {secret['path']}")
|
|
83
|
+
info(f"Version: {secret['version']}")
|
|
84
|
+
|
|
85
|
+
except Exception as e:
|
|
86
|
+
error(f"Failed to update secret: {e}")
|
|
87
|
+
raise typer.Exit(1)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@app.command()
|
|
91
|
+
def delete(
|
|
92
|
+
path: str = typer.Argument(..., help="Secret path"),
|
|
93
|
+
force: bool = typer.Option(False, "--force", "-f", help="Force deletion"),
|
|
94
|
+
):
|
|
95
|
+
"""Delete a secret."""
|
|
96
|
+
try:
|
|
97
|
+
if not force:
|
|
98
|
+
if not typer.confirm(f"Are you sure you want to delete secret {path}?"):
|
|
99
|
+
info("Deletion cancelled")
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
with get_client() as client:
|
|
103
|
+
client.delete(f"/api/v1/secrets/{path}")
|
|
104
|
+
|
|
105
|
+
success(f"Secret deleted: {path}")
|
|
106
|
+
|
|
107
|
+
except Exception as e:
|
|
108
|
+
error(f"Failed to delete secret: {e}")
|
|
109
|
+
raise typer.Exit(1)
|
af_cli/commands/tools.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tool management commands for the Agentic Fabric CLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from af_cli.core.client import get_client
|
|
9
|
+
from af_cli.core.output import error, info, print_output, success, warning
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(help="Tool management commands")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@app.command()
|
|
15
|
+
def list(
|
|
16
|
+
format: str = typer.Option("table", "--format", "-f", help="Output format"),
|
|
17
|
+
):
|
|
18
|
+
"""List tools."""
|
|
19
|
+
try:
|
|
20
|
+
with get_client() as client:
|
|
21
|
+
response = client.get("/api/v1/tools")
|
|
22
|
+
tools = response["tools"]
|
|
23
|
+
|
|
24
|
+
if not tools:
|
|
25
|
+
warning("No tools found")
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
print_output(
|
|
29
|
+
tools,
|
|
30
|
+
format_type=format,
|
|
31
|
+
title="Tools"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
except Exception as e:
|
|
35
|
+
error(f"Failed to list tools: {e}")
|
|
36
|
+
raise typer.Exit(1)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@app.command()
|
|
40
|
+
def get(
|
|
41
|
+
tool_id: str = typer.Argument(..., help="Tool ID"),
|
|
42
|
+
format: str = typer.Option("table", "--format", "-f", help="Output format"),
|
|
43
|
+
):
|
|
44
|
+
"""Get tool details."""
|
|
45
|
+
try:
|
|
46
|
+
with get_client() as client:
|
|
47
|
+
tool = client.get(f"/api/v1/tools/{tool_id}")
|
|
48
|
+
|
|
49
|
+
print_output(
|
|
50
|
+
tool,
|
|
51
|
+
format_type=format,
|
|
52
|
+
title=f"Tool {tool_id}"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
except Exception as e:
|
|
56
|
+
error(f"Failed to get tool: {e}")
|
|
57
|
+
raise typer.Exit(1)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@app.command()
|
|
61
|
+
def invoke(
|
|
62
|
+
tool_id: str = typer.Argument(..., help="Tool ID"),
|
|
63
|
+
method: str = typer.Option(..., "--method", "-m", help="Tool method to invoke"),
|
|
64
|
+
format: str = typer.Option("table", "--format", "-f", help="Output format"),
|
|
65
|
+
):
|
|
66
|
+
"""Invoke a tool."""
|
|
67
|
+
try:
|
|
68
|
+
with get_client() as client:
|
|
69
|
+
data = {
|
|
70
|
+
"method": method,
|
|
71
|
+
"parameters": {},
|
|
72
|
+
"context": {},
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
info(f"Invoking tool {tool_id} method {method}...")
|
|
76
|
+
response = client.post(f"/api/v1/tools/{tool_id}/invoke", data)
|
|
77
|
+
|
|
78
|
+
success("Tool invoked successfully")
|
|
79
|
+
print_output(response, format_type=format)
|
|
80
|
+
|
|
81
|
+
except Exception as e:
|
|
82
|
+
error(f"Failed to invoke tool: {e}")
|
|
83
|
+
raise typer.Exit(1)
|
af_cli/core/__init__.py
ADDED
af_cli/core/client.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HTTP client for communicating with the Agentic Fabric Gateway.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any, Dict, Optional
|
|
7
|
+
from urllib.parse import urljoin
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
import typer
|
|
11
|
+
|
|
12
|
+
from af_cli.core.config import get_config
|
|
13
|
+
from af_cli.core.output import debug, error
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AFClient:
|
|
17
|
+
"""HTTP client for Agentic Fabric Gateway API."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, config: Optional[Dict] = None):
|
|
20
|
+
self.config = config or get_config()
|
|
21
|
+
self.client = httpx.Client(
|
|
22
|
+
base_url=self.config.gateway_url,
|
|
23
|
+
timeout=30.0,
|
|
24
|
+
follow_redirects=True,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def _get_headers(self) -> Dict[str, str]:
|
|
28
|
+
"""Get HTTP headers for requests."""
|
|
29
|
+
return self.config.get_headers()
|
|
30
|
+
|
|
31
|
+
def _handle_response(self, response: httpx.Response) -> Dict[str, Any]:
|
|
32
|
+
"""Handle HTTP response."""
|
|
33
|
+
debug(f"Response: {response.status_code} {response.url}")
|
|
34
|
+
|
|
35
|
+
if response.status_code == 401:
|
|
36
|
+
error("Authentication failed. Please run 'af auth login'")
|
|
37
|
+
raise typer.Exit(1)
|
|
38
|
+
|
|
39
|
+
if response.status_code == 403:
|
|
40
|
+
error("Access denied. Check your permissions.")
|
|
41
|
+
raise typer.Exit(1)
|
|
42
|
+
|
|
43
|
+
if response.status_code >= 400:
|
|
44
|
+
try:
|
|
45
|
+
error_data = response.json()
|
|
46
|
+
error_message = error_data.get("message", "Unknown error")
|
|
47
|
+
error(f"API Error: {error_message}")
|
|
48
|
+
if self.config.verbose and "details" in error_data:
|
|
49
|
+
debug(f"Error details: {json.dumps(error_data['details'], indent=2)}")
|
|
50
|
+
except:
|
|
51
|
+
error(f"HTTP Error: {response.status_code} {response.text}")
|
|
52
|
+
raise typer.Exit(1)
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
return response.json()
|
|
56
|
+
except:
|
|
57
|
+
return {"message": "Success"}
|
|
58
|
+
|
|
59
|
+
def get(self, path: str, params: Optional[Dict] = None) -> Dict[str, Any]:
|
|
60
|
+
"""Make GET request."""
|
|
61
|
+
url = urljoin(self.config.gateway_url, path)
|
|
62
|
+
debug(f"GET {url}")
|
|
63
|
+
|
|
64
|
+
response = self.client.get(
|
|
65
|
+
path,
|
|
66
|
+
params=params,
|
|
67
|
+
headers=self._get_headers(),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return self._handle_response(response)
|
|
71
|
+
|
|
72
|
+
def post(self, path: str, data: Optional[Dict] = None) -> Dict[str, Any]:
|
|
73
|
+
"""Make POST request."""
|
|
74
|
+
url = urljoin(self.config.gateway_url, path)
|
|
75
|
+
debug(f"POST {url}")
|
|
76
|
+
|
|
77
|
+
response = self.client.post(
|
|
78
|
+
path,
|
|
79
|
+
json=data,
|
|
80
|
+
headers=self._get_headers(),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return self._handle_response(response)
|
|
84
|
+
|
|
85
|
+
def put(self, path: str, data: Optional[Dict] = None) -> Dict[str, Any]:
|
|
86
|
+
"""Make PUT request."""
|
|
87
|
+
url = urljoin(self.config.gateway_url, path)
|
|
88
|
+
debug(f"PUT {url}")
|
|
89
|
+
|
|
90
|
+
response = self.client.put(
|
|
91
|
+
path,
|
|
92
|
+
json=data,
|
|
93
|
+
headers=self._get_headers(),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return self._handle_response(response)
|
|
97
|
+
|
|
98
|
+
def delete(self, path: str) -> Dict[str, Any]:
|
|
99
|
+
"""Make DELETE request."""
|
|
100
|
+
url = urljoin(self.config.gateway_url, path)
|
|
101
|
+
debug(f"DELETE {url}")
|
|
102
|
+
|
|
103
|
+
response = self.client.delete(
|
|
104
|
+
path,
|
|
105
|
+
headers=self._get_headers(),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
return self._handle_response(response)
|
|
109
|
+
|
|
110
|
+
def close(self) -> None:
|
|
111
|
+
"""Close the HTTP client."""
|
|
112
|
+
self.client.close()
|
|
113
|
+
|
|
114
|
+
def __enter__(self):
|
|
115
|
+
return self
|
|
116
|
+
|
|
117
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
118
|
+
self.close()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def get_client() -> AFClient:
|
|
122
|
+
"""Get HTTP client instance."""
|
|
123
|
+
return AFClient()
|