fastsdk 0.3.1__tar.gz → 0.4.2__tar.gz
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.
- {fastsdk-0.3.1 → fastsdk-0.4.2}/PKG-INFO +5 -5
- {fastsdk-0.3.1 → fastsdk-0.4.2}/README.md +2 -2
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/__init__.py +1 -1
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/api.py +22 -21
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/cli.py +47 -37
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/fastClient.py +41 -28
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/fastSDK.py +126 -118
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/fastStub.py +13 -13
- fastsdk-0.4.2/fastsdk/requires.py +36 -0
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/sdk_factory/sdk_factory.py +27 -25
- fastsdk-0.4.2/fastsdk/service_access.py +40 -0
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/api_job_manager.py +8 -8
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/api_seex.py +12 -12
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/job_tasks.py +8 -8
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/pipeline_planner.py +9 -8
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/provider_factory.py +25 -30
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/provider_stack_registry.py +12 -12
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/request/api_client.py +20 -12
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/request/api_client_replicate.py +5 -4
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/request/api_client_runpod.py +6 -5
- fastsdk-0.4.2/fastsdk/service_interaction/request/api_client_socaity.py +150 -0
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_specification_loader/replicate_loader.py +22 -16
- fastsdk-0.4.2/fastsdk/service_specification_loader/runpod_open_api_loader.py +68 -0
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk.egg-info/PKG-INFO +5 -5
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk.egg-info/SOURCES.txt +2 -0
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk.egg-info/requires.txt +2 -2
- {fastsdk-0.3.1 → fastsdk-0.4.2}/pyproject.toml +3 -3
- {fastsdk-0.3.1 → fastsdk-0.4.2}/test/test_apipod_debug_test_services.py +23 -22
- fastsdk-0.4.2/test/test_apipod_registry_service_management_example.py +118 -0
- {fastsdk-0.3.1 → fastsdk-0.4.2}/test/test_client_factory.py +2 -2
- {fastsdk-0.3.1 → fastsdk-0.4.2}/test/test_client_factory_manual.py +21 -16
- {fastsdk-0.3.1 → fastsdk-0.4.2}/test/test_client_from_runpod_serverless.py +2 -3
- {fastsdk-0.3.1 → fastsdk-0.4.2}/test/test_localhost_services.py +2 -2
- {fastsdk-0.3.1 → fastsdk-0.4.2}/test/test_manual_sdk.py +2 -3
- {fastsdk-0.3.1 → fastsdk-0.4.2}/test/test_replicate.py +1 -1
- fastsdk-0.3.1/fastsdk/service_interaction/request/api_client_socaity.py +0 -79
- fastsdk-0.3.1/fastsdk/service_specification_loader/runpod_open_api_loader.py +0 -72
- fastsdk-0.3.1/test/test_apipod_registry_service_management_example.py +0 -161
- {fastsdk-0.3.1 → fastsdk-0.4.2}/LICENSE +0 -0
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/sdk_factory/__init__.py +0 -0
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/sdk_factory/sdk_template.j2 +0 -0
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/__init__.py +0 -0
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/async_bridge.py +0 -0
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/job_runtime.py +0 -0
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/request/__init__.py +0 -0
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/request/file_handler.py +0 -0
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/response/api_job_status.py +0 -0
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/response/response_parser.py +0 -0
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/response/response_schemas.py +0 -0
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/response/sse_assembly.py +0 -0
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/response/stream_session.py +0 -0
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_specification_loader/spec_loader.py +0 -0
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk.egg-info/dependency_links.txt +0 -0
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk.egg-info/entry_points.txt +0 -0
- {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk.egg-info/top_level.txt +0 -0
- {fastsdk-0.3.1 → fastsdk-0.4.2}/setup.cfg +0 -0
- {fastsdk-0.3.1 → fastsdk-0.4.2}/test/test_client_from_fastapi.py +0 -0
- {fastsdk-0.3.1 → fastsdk-0.4.2}/test/test_httpx_api.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastsdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Turn any AI/web service (OpenAPI, Replicate, RunPod, APIPod) into a native-feeling Python client.
|
|
5
5
|
Author: SocAIty
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
@@ -691,8 +691,8 @@ Requires-Dist: media-toolkit>=00.02.22
|
|
|
691
691
|
Requires-Dist: singleton_decorator==1.0.0
|
|
692
692
|
Requires-Dist: jinja2>=3.1.6
|
|
693
693
|
Requires-Dist: fastcloud>=0.0.14
|
|
694
|
-
Requires-Dist: apipod-registry>=0.
|
|
695
|
-
Requires-Dist: socaity-schemas>=0.
|
|
694
|
+
Requires-Dist: apipod-registry>=0.1.1
|
|
695
|
+
Requires-Dist: socaity-schemas>=0.1.1
|
|
696
696
|
Provides-Extra: dev
|
|
697
697
|
Requires-Dist: pytest; extra == "dev"
|
|
698
698
|
Provides-Extra: replicate
|
|
@@ -811,8 +811,8 @@ Pass `api_key=...` to `connect()`, `generate_stub()` or the client constructor
|
|
|
811
811
|
|
|
812
812
|
| Concept | What it is |
|
|
813
813
|
|---|---|
|
|
814
|
-
| **Service
|
|
815
|
-
| **Registry** | An in-process directory of
|
|
814
|
+
| **Service** | An `AIService` (from socaity-schemas) with one deployment: hosting provider, address, and the parsed `ServiceContract` (endpoints, parameters, whether responses are polled jobs). Get one with `fastsdk.inspect_service(source)`, it has no side effects. |
|
|
815
|
+
| **Registry** | An in-process directory of services, shared by all clients. `register_service()` adds to it; generated stubs look their service up in it by ID. |
|
|
816
816
|
| **Client** | The runtime object you call (`FastClient`). It submits jobs to the service. `connect()` gives you a generic one instantly. |
|
|
817
817
|
| **Stub** | A generated `.py` file containing a client subclass with one typed method per endpoint. Made by `generate_stub()`; it's plain code — read it, version it, ship it. |
|
|
818
818
|
|
|
@@ -110,8 +110,8 @@ Pass `api_key=...` to `connect()`, `generate_stub()` or the client constructor
|
|
|
110
110
|
|
|
111
111
|
| Concept | What it is |
|
|
112
112
|
|---|---|
|
|
113
|
-
| **Service
|
|
114
|
-
| **Registry** | An in-process directory of
|
|
113
|
+
| **Service** | An `AIService` (from socaity-schemas) with one deployment: hosting provider, address, and the parsed `ServiceContract` (endpoints, parameters, whether responses are polled jobs). Get one with `fastsdk.inspect_service(source)`, it has no side effects. |
|
|
114
|
+
| **Registry** | An in-process directory of services, shared by all clients. `register_service()` adds to it; generated stubs look their service up in it by ID. |
|
|
115
115
|
| **Client** | The runtime object you call (`FastClient`). It submits jobs to the service. `connect()` gives you a generic one instantly. |
|
|
116
116
|
| **Stub** | A generated `.py` file containing a client subclass with one typed method per endpoint. Made by `generate_stub()`; it's plain code — read it, version it, ship it. |
|
|
117
117
|
|
|
@@ -7,13 +7,13 @@ These functions wrap the FastSDK singleton so users never have to deal with it d
|
|
|
7
7
|
|
|
8
8
|
client = fastsdk.connect("http://localhost:8009") # use a service right now
|
|
9
9
|
stub = fastsdk.generate_stub("http://localhost:8009") # generate a client stub file
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
service = fastsdk.inspect_service("replicate:owner/name") # look at a service without side effects
|
|
11
|
+
service = fastsdk.register_service("./openapi.json") # add a service to the registry
|
|
12
12
|
"""
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
from typing import Any, Dict, List, Optional, Union, TYPE_CHECKING
|
|
15
15
|
|
|
16
|
-
from socaity_schemas.
|
|
16
|
+
from socaity_schemas.platform import AIService
|
|
17
17
|
|
|
18
18
|
from fastsdk.fastSDK import FastSDK
|
|
19
19
|
|
|
@@ -23,7 +23,7 @@ if TYPE_CHECKING:
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def connect(
|
|
26
|
-
source: Union[str, Path, Dict[str, Any],
|
|
26
|
+
source: Union[str, Path, Dict[str, Any], AIService],
|
|
27
27
|
api_key: Optional[str] = None,
|
|
28
28
|
**kwargs
|
|
29
29
|
) -> 'FastClient':
|
|
@@ -31,7 +31,7 @@ def connect(
|
|
|
31
31
|
Connect to a service and get a ready-to-use client - no code generation, no files.
|
|
32
32
|
|
|
33
33
|
Args:
|
|
34
|
-
source: Service URL ("http://localhost:8009"), openapi.json path/dict,
|
|
34
|
+
source: Service URL ("http://localhost:8009"), openapi.json path/dict, AIService,
|
|
35
35
|
Replicate model reference ("replicate:owner/name") or a registered service ID/name.
|
|
36
36
|
api_key: Optional API key. Falls back to environment variables.
|
|
37
37
|
**kwargs: Additional service loading arguments (see inspect_service).
|
|
@@ -48,27 +48,28 @@ def connect(
|
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
def inspect_service(
|
|
51
|
-
source: Union[str, Path, Dict[str, Any],
|
|
51
|
+
source: Union[str, Path, Dict[str, Any], AIService],
|
|
52
52
|
api_key: Optional[str] = None,
|
|
53
53
|
**kwargs
|
|
54
|
-
) ->
|
|
54
|
+
) -> AIService:
|
|
55
55
|
"""
|
|
56
|
-
Load and parse a service into
|
|
56
|
+
Load and parse a service into an AIService without registering it anywhere.
|
|
57
57
|
Pure function: no side effects on the registry.
|
|
58
58
|
|
|
59
59
|
Args:
|
|
60
|
-
source: Service URL, openapi.json path/dict, Replicate model reference or
|
|
60
|
+
source: Service URL, openapi.json path/dict, Replicate model reference or AIService.
|
|
61
61
|
api_key: Required for RunPod and Replicate sources.
|
|
62
|
-
**kwargs: Overrides such as
|
|
62
|
+
**kwargs: Overrides such as provider, service_id, name.
|
|
63
63
|
|
|
64
64
|
Returns:
|
|
65
|
-
|
|
65
|
+
AIService whose primary deployment carries the parsed ServiceContract
|
|
66
|
+
(endpoints, parameters) and the resolved service address.
|
|
66
67
|
"""
|
|
67
68
|
return FastSDK.inspect_service(source, api_key=api_key, **kwargs)
|
|
68
69
|
|
|
69
70
|
|
|
70
71
|
def generate_stub(
|
|
71
|
-
source: Union[str, Path, Dict[str, Any],
|
|
72
|
+
source: Union[str, Path, Dict[str, Any], AIService],
|
|
72
73
|
save_path: Optional[str] = None,
|
|
73
74
|
class_name: Optional[str] = None,
|
|
74
75
|
template: Optional[str] = None,
|
|
@@ -80,7 +81,7 @@ def generate_stub(
|
|
|
80
81
|
used immediately in the same process.
|
|
81
82
|
|
|
82
83
|
Args:
|
|
83
|
-
source: Service URL, openapi.json path/dict,
|
|
84
|
+
source: Service URL, openapi.json path/dict, AIService, Replicate model
|
|
84
85
|
reference or a registered service ID/name.
|
|
85
86
|
save_path: File or directory path for the generated .py file. Defaults to the current directory.
|
|
86
87
|
class_name: Name of the generated class. Defaults to the (normalized) service name.
|
|
@@ -88,7 +89,7 @@ def generate_stub(
|
|
|
88
89
|
**kwargs: Additional service loading arguments (e.g. api_key, service_name).
|
|
89
90
|
|
|
90
91
|
Returns:
|
|
91
|
-
|
|
92
|
+
FastStub with .path, .class_name, .service and .client().
|
|
92
93
|
|
|
93
94
|
Example:
|
|
94
95
|
stub = fastsdk.generate_stub("http://localhost:8009", save_path="clients/")
|
|
@@ -100,29 +101,29 @@ def generate_stub(
|
|
|
100
101
|
|
|
101
102
|
|
|
102
103
|
def register_service(
|
|
103
|
-
source: Union[str, Path, Dict[str, Any],
|
|
104
|
+
source: Union[str, Path, Dict[str, Any], AIService],
|
|
104
105
|
**kwargs
|
|
105
|
-
) ->
|
|
106
|
+
) -> AIService:
|
|
106
107
|
"""
|
|
107
108
|
Load a service and add it to the registry. Idempotent: re-registering a service with the
|
|
108
109
|
same ID replaces the previous entry.
|
|
109
110
|
|
|
110
111
|
Args:
|
|
111
|
-
source: Service URL, openapi.json path/dict, Replicate model reference or
|
|
112
|
-
**kwargs: Overrides such as service_name, service_id, service_address, api_key, ...
|
|
112
|
+
source: Service URL, openapi.json path/dict, Replicate model reference or AIService.
|
|
113
|
+
**kwargs: Overrides such as service_name, service_id, service_address, provider, api_key, ...
|
|
113
114
|
|
|
114
115
|
Returns:
|
|
115
|
-
The registered
|
|
116
|
+
The registered AIService.
|
|
116
117
|
"""
|
|
117
118
|
return FastSDK().register_service(source, **kwargs)
|
|
118
119
|
|
|
119
120
|
|
|
120
|
-
def get_service(service_id_or_name: str) -> Optional[
|
|
121
|
+
def get_service(service_id_or_name: str) -> Optional[AIService]:
|
|
121
122
|
"""Get a registered service by ID or name. Returns None if not found."""
|
|
122
123
|
return FastSDK().get_service(service_id_or_name)
|
|
123
124
|
|
|
124
125
|
|
|
125
|
-
def list_services() -> List[
|
|
126
|
+
def list_services() -> List[AIService]:
|
|
126
127
|
"""List all services currently in the registry."""
|
|
127
128
|
return FastSDK().service_registry.list_services()
|
|
128
129
|
|
|
@@ -18,9 +18,11 @@ import sys
|
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
from typing import Any, Dict, List, Optional, Union
|
|
20
20
|
|
|
21
|
-
from apipod_registry import Registry
|
|
22
|
-
from socaity_schemas.
|
|
23
|
-
from
|
|
21
|
+
from apipod_registry import FileSystemStore, Registry
|
|
22
|
+
from socaity_schemas.contract.address import service_url
|
|
23
|
+
from socaity_schemas.platform import AIService
|
|
24
|
+
|
|
25
|
+
from fastsdk.service_access import service_address, service_contract, service_provider
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
DEFAULT_REGISTRY_PATH = Path.home() / ".fastsdk" / "registry"
|
|
@@ -38,8 +40,8 @@ def _open_persistent_registry() -> Registry:
|
|
|
38
40
|
return Registry(service_store=FileSystemStore(path=str(_registry_path())))
|
|
39
41
|
|
|
40
42
|
|
|
41
|
-
def _resolve_source(source: str) -> Union[str,
|
|
42
|
-
"""If the source matches a service in the persistent CLI registry, use that
|
|
43
|
+
def _resolve_source(source: str) -> Union[str, AIService]:
|
|
44
|
+
"""If the source matches a service in the persistent CLI registry, use that service."""
|
|
43
45
|
try:
|
|
44
46
|
stored = _open_persistent_registry().get_service(source)
|
|
45
47
|
except Exception:
|
|
@@ -47,27 +49,37 @@ def _resolve_source(source: str) -> Union[str, ServiceDefinition]:
|
|
|
47
49
|
return stored or source
|
|
48
50
|
|
|
49
51
|
|
|
50
|
-
def
|
|
51
|
-
address =
|
|
52
|
-
return
|
|
52
|
+
def _address_url(service: AIService) -> str:
|
|
53
|
+
address = service_address(service)
|
|
54
|
+
return service_url(address) if address else "-"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _service_summary(service: AIService) -> str:
|
|
58
|
+
contract = service_contract(service)
|
|
59
|
+
return (
|
|
60
|
+
f"{service.display_name} (id: {service.id}, spec: {contract.specification}, "
|
|
61
|
+
f"provider: {service_provider(service)}, address: {_address_url(service)})"
|
|
62
|
+
)
|
|
53
63
|
|
|
54
64
|
|
|
55
|
-
def _print_service(
|
|
65
|
+
def _print_service(service: AIService, as_json: bool = False):
|
|
56
66
|
if as_json:
|
|
57
|
-
print(json.dumps(
|
|
67
|
+
print(json.dumps(service.model_dump(exclude_none=True), indent=2, default=str))
|
|
58
68
|
return
|
|
59
69
|
|
|
60
70
|
from fastsdk.sdk_factory.sdk_factory import _get_type_hint
|
|
61
71
|
|
|
62
|
-
|
|
63
|
-
print(f"
|
|
64
|
-
print(f"
|
|
65
|
-
print(f"
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
contract = service_contract(service)
|
|
73
|
+
print(f"Service: {service.display_name}")
|
|
74
|
+
print(f"ID: {service.id}")
|
|
75
|
+
print(f"Spec: {contract.specification}")
|
|
76
|
+
print(f"Provider: {service_provider(service)}")
|
|
77
|
+
print(f"Address: {_address_url(service)}")
|
|
78
|
+
if service.description:
|
|
79
|
+
desc = service.description.strip().split("\n")[0]
|
|
68
80
|
print(f"About: {desc[:120]}")
|
|
69
81
|
print("Endpoints:")
|
|
70
|
-
for endpoint in
|
|
82
|
+
for endpoint in contract.endpoints:
|
|
71
83
|
print(f" {endpoint.path}")
|
|
72
84
|
for param in endpoint.parameters:
|
|
73
85
|
if param.location not in ("body", "query"):
|
|
@@ -150,8 +162,8 @@ def _import_hint(stub_path: str, class_name: str) -> str:
|
|
|
150
162
|
|
|
151
163
|
def cmd_inspect(args: argparse.Namespace):
|
|
152
164
|
import fastsdk
|
|
153
|
-
|
|
154
|
-
_print_service(
|
|
165
|
+
service = fastsdk.inspect_service(_resolve_source(args.source), api_key=args.api_key)
|
|
166
|
+
_print_service(service, as_json=args.json)
|
|
155
167
|
|
|
156
168
|
|
|
157
169
|
def cmd_generate(args: argparse.Namespace):
|
|
@@ -167,10 +179,9 @@ def cmd_generate(args: argparse.Namespace):
|
|
|
167
179
|
api_key=args.api_key,
|
|
168
180
|
**kwargs
|
|
169
181
|
)
|
|
170
|
-
sd = stub.service_definition
|
|
171
182
|
print(f"Generated {stub.path}")
|
|
172
183
|
print(f" class: {stub.class_name}")
|
|
173
|
-
print(f" service: {_service_summary(
|
|
184
|
+
print(f" service: {_service_summary(stub.service)}")
|
|
174
185
|
print()
|
|
175
186
|
print("Use it:")
|
|
176
187
|
print(f" {_import_hint(stub.path, stub.class_name)}")
|
|
@@ -182,17 +193,18 @@ def cmd_call(args: argparse.Namespace, extra: List[str]):
|
|
|
182
193
|
|
|
183
194
|
params = _parse_endpoint_params(extra)
|
|
184
195
|
client = FastClient(_resolve_source(args.source), api_key=args.api_key)
|
|
185
|
-
|
|
196
|
+
service = client.service
|
|
197
|
+
endpoints = service_contract(service).endpoints
|
|
186
198
|
|
|
187
199
|
endpoint = args.endpoint
|
|
188
200
|
if not endpoint:
|
|
189
|
-
if len(
|
|
190
|
-
endpoint =
|
|
201
|
+
if len(endpoints) == 1:
|
|
202
|
+
endpoint = endpoints[0].path
|
|
191
203
|
else:
|
|
192
|
-
paths = "\n ".join(ep.path for ep in
|
|
204
|
+
paths = "\n ".join(ep.path for ep in endpoints)
|
|
193
205
|
raise SystemExit(f"Service has multiple endpoints, please specify one:\n {paths}")
|
|
194
206
|
|
|
195
|
-
print(f"Calling {endpoint} on {
|
|
207
|
+
print(f"Calling {endpoint} on {service.display_name} ...")
|
|
196
208
|
try:
|
|
197
209
|
job = client.submit_job(endpoint, **params)
|
|
198
210
|
result = job.wait_for_result()
|
|
@@ -216,20 +228,18 @@ def cmd_registry(args: argparse.Namespace):
|
|
|
216
228
|
if not services:
|
|
217
229
|
print(f"No services registered ({_registry_path()}).")
|
|
218
230
|
return
|
|
219
|
-
for
|
|
220
|
-
print(f" {_service_summary(
|
|
231
|
+
for service in services:
|
|
232
|
+
print(f" {_service_summary(service)}")
|
|
221
233
|
|
|
222
234
|
elif args.registry_command == "add":
|
|
223
|
-
|
|
235
|
+
service = fastsdk.inspect_service(
|
|
224
236
|
args.source,
|
|
225
237
|
service_id=args.id,
|
|
226
|
-
|
|
238
|
+
name=args.name,
|
|
227
239
|
api_key=args.api_key
|
|
228
240
|
)
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
registry.register_service(sd)
|
|
232
|
-
print(f"Registered {_service_summary(sd)}")
|
|
241
|
+
registry.add_service(service)
|
|
242
|
+
print(f"Registered {_service_summary(service)}")
|
|
233
243
|
print(f"Registry: {_registry_path()}")
|
|
234
244
|
|
|
235
245
|
elif args.registry_command == "remove":
|
|
@@ -239,10 +249,10 @@ def cmd_registry(args: argparse.Namespace):
|
|
|
239
249
|
raise SystemExit(f"Service '{args.id}' not found in registry ({_registry_path()}).")
|
|
240
250
|
|
|
241
251
|
elif args.registry_command == "show":
|
|
242
|
-
|
|
243
|
-
if not
|
|
252
|
+
service = registry.get_service(args.id)
|
|
253
|
+
if not service:
|
|
244
254
|
raise SystemExit(f"Service '{args.id}' not found in registry ({_registry_path()}).")
|
|
245
|
-
_print_service(
|
|
255
|
+
_print_service(service, as_json=args.json)
|
|
246
256
|
|
|
247
257
|
else:
|
|
248
258
|
raise SystemExit("Usage: fastsdk registry {list,add,remove,show}")
|
|
@@ -2,13 +2,22 @@ from pathlib import Path
|
|
|
2
2
|
from typing import Any, Dict, Optional, Union, TYPE_CHECKING
|
|
3
3
|
import os
|
|
4
4
|
|
|
5
|
-
from socaity_schemas.
|
|
5
|
+
from socaity_schemas.platform import AIService, PriceEstimate
|
|
6
|
+
|
|
6
7
|
from fastsdk.fastSDK import FastSDK
|
|
8
|
+
from fastsdk.service_access import service_provider
|
|
7
9
|
|
|
8
10
|
if TYPE_CHECKING:
|
|
9
11
|
from fastsdk.service_interaction.api_seex import APISeex
|
|
10
12
|
|
|
11
13
|
|
|
14
|
+
_PROVIDER_API_KEY_ENV = {
|
|
15
|
+
"socaity": "SOCAITY_API_KEY",
|
|
16
|
+
"runpod": "RUNPOD_API_KEY",
|
|
17
|
+
"replicate": "REPLICATE_API_KEY",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
12
21
|
class FastClient:
|
|
13
22
|
"""
|
|
14
23
|
The runtime client for a service. Generated stubs inherit from this class, but it can also be
|
|
@@ -16,7 +25,7 @@ class FastClient:
|
|
|
16
25
|
|
|
17
26
|
A FastClient can be created from:
|
|
18
27
|
- a registered service ID or name (this is what generated stubs do)
|
|
19
|
-
- a service URL, an openapi.json file path, a spec dict or
|
|
28
|
+
- a service URL, an openapi.json file path, a spec dict or an AIService
|
|
20
29
|
- a Replicate model reference like "replicate:owner/name"
|
|
21
30
|
|
|
22
31
|
If the source is not yet in the registry, it is loaded and registered automatically.
|
|
@@ -27,7 +36,7 @@ class FastClient:
|
|
|
27
36
|
"""
|
|
28
37
|
def __init__(
|
|
29
38
|
self,
|
|
30
|
-
service: Union[str, Path, Dict[str, Any],
|
|
39
|
+
service: Union[str, Path, Dict[str, Any], AIService, None] = None,
|
|
31
40
|
api_key: Optional[str] = None,
|
|
32
41
|
temporary: bool = False,
|
|
33
42
|
service_name_or_id: Optional[str] = None,
|
|
@@ -36,7 +45,7 @@ class FastClient:
|
|
|
36
45
|
"""
|
|
37
46
|
Args:
|
|
38
47
|
service: Service source. A registered service ID/name, a URL, a spec file path,
|
|
39
|
-
a spec dict,
|
|
48
|
+
a spec dict, an AIService or a Replicate model reference.
|
|
40
49
|
api_key: Optional API key for the service. If not provided, it is looked up in
|
|
41
50
|
the environment variables (e.g. REPLICATE_API_KEY, RUNPOD_API_KEY, <SERVICE_ID>_API_KEY).
|
|
42
51
|
temporary: If True, the service is removed from the registry when the client is deleted.
|
|
@@ -48,25 +57,23 @@ class FastClient:
|
|
|
48
57
|
self.temporary = temporary
|
|
49
58
|
|
|
50
59
|
if service is None and service_name_or_id is None:
|
|
51
|
-
raise ValueError("Provide a service source (URL, file, dict,
|
|
60
|
+
raise ValueError("Provide a service source (URL, file, dict, AIService) or a registered service ID/name.")
|
|
52
61
|
|
|
53
62
|
if service_name_or_id is not None:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if not self.service_definition:
|
|
63
|
+
self.service = self.fsdk.service_registry.get_service(service_name_or_id)
|
|
64
|
+
if not self.service:
|
|
57
65
|
raise ValueError(
|
|
58
66
|
f"Service '{service_name_or_id}' not found in the registry. "
|
|
59
67
|
f"Register it first with fastsdk.register_service(...) or regenerate the stub with fastsdk.generate_stub(...)."
|
|
60
68
|
)
|
|
61
69
|
else:
|
|
62
|
-
self.
|
|
70
|
+
self.service = self._resolve_service(service, api_key, **load_kwargs)
|
|
63
71
|
|
|
64
72
|
# try to get api key from global settings if not provided
|
|
65
73
|
self.api_key = api_key or self._get_api_key()
|
|
66
|
-
|
|
67
|
-
self.fsdk.provider_stacks.load(self.service_definition.id, self.api_key)
|
|
74
|
+
self.fsdk.provider_stacks.load(self.service.id, self.api_key)
|
|
68
75
|
|
|
69
|
-
def _resolve_service(self, service, api_key: Optional[str], **load_kwargs) ->
|
|
76
|
+
def _resolve_service(self, service, api_key: Optional[str], **load_kwargs) -> AIService:
|
|
70
77
|
# A plain string might be a registered service ID/name - check the registry first.
|
|
71
78
|
if isinstance(service, str):
|
|
72
79
|
registered = self.fsdk.service_registry.get_service(service)
|
|
@@ -75,31 +82,37 @@ class FastClient:
|
|
|
75
82
|
# Otherwise treat it as a spec source: load and register it.
|
|
76
83
|
# Temporary clients always get their own registry entry (update_existing=False),
|
|
77
84
|
# so removing it on cleanup never deletes a permanently registered service.
|
|
78
|
-
|
|
79
|
-
return service_def
|
|
85
|
+
return self.fsdk.register_service(service, api_key=api_key, update_existing=not self.temporary, **load_kwargs)
|
|
80
86
|
|
|
81
87
|
def _get_api_key(self):
|
|
82
|
-
|
|
83
|
-
if
|
|
84
|
-
return os.getenv(
|
|
85
|
-
|
|
86
|
-
return os.getenv("RUNPOD_API_KEY", None)
|
|
87
|
-
elif isinstance(self.service_definition.service_address, ReplicateServiceAddress):
|
|
88
|
-
return os.getenv("REPLICATE_API_KEY", None)
|
|
89
|
-
|
|
90
|
-
# for locals try by service_id
|
|
91
|
-
return os.getenv(self.service_definition.id.upper() + "_API_KEY", None)
|
|
88
|
+
env_var = _PROVIDER_API_KEY_ENV.get(service_provider(self.service))
|
|
89
|
+
if env_var:
|
|
90
|
+
return os.getenv(env_var, None)
|
|
91
|
+
return os.getenv(self.service.id.upper() + "_API_KEY", None)
|
|
92
92
|
|
|
93
93
|
def submit_job(self, endpoint_id: str, **kwargs) -> 'APISeex':
|
|
94
|
-
return self.fsdk.api_job_manager.submit_job(self.
|
|
94
|
+
return self.fsdk.api_job_manager.submit_job(self.service.id, endpoint_id, data=kwargs)
|
|
95
|
+
|
|
96
|
+
def estimate(self, endpoint_path: str, **params) -> PriceEstimate:
|
|
97
|
+
"""Estimate price and runtime. Implemented by the Socaity provider API client.
|
|
98
|
+
|
|
99
|
+
Requires ``socaity-cli`` when the provider is Socaity (signaled via ``@requires``).
|
|
100
|
+
"""
|
|
101
|
+
stack = self.fsdk.provider_stacks.require(self.service.id)
|
|
102
|
+
estimate_fn = getattr(stack.api_client, "estimate", None)
|
|
103
|
+
if estimate_fn is None:
|
|
104
|
+
raise NotImplementedError(
|
|
105
|
+
"estimate() is only available for Socaity-hosted services "
|
|
106
|
+
f"(provider client: {type(stack.api_client).__name__})"
|
|
107
|
+
)
|
|
108
|
+
return estimate_fn(endpoint_path, **params)
|
|
95
109
|
|
|
96
110
|
def close(self):
|
|
97
111
|
"""Remove the service from the registry if this client registered it temporarily."""
|
|
98
|
-
if self.temporary and getattr(self, '
|
|
112
|
+
if self.temporary and getattr(self, 'service', None) and hasattr(self, 'fsdk'):
|
|
99
113
|
try:
|
|
100
|
-
self.fsdk.service_registry.remove_service(self.
|
|
114
|
+
self.fsdk.service_registry.remove_service(self.service.id)
|
|
101
115
|
except Exception:
|
|
102
|
-
# Ignore errors during cleanup (e.g., if FastSDK is already destroyed)
|
|
103
116
|
pass
|
|
104
117
|
self.temporary = False
|
|
105
118
|
|