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.
Files changed (58) hide show
  1. {fastsdk-0.3.1 → fastsdk-0.4.2}/PKG-INFO +5 -5
  2. {fastsdk-0.3.1 → fastsdk-0.4.2}/README.md +2 -2
  3. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/__init__.py +1 -1
  4. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/api.py +22 -21
  5. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/cli.py +47 -37
  6. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/fastClient.py +41 -28
  7. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/fastSDK.py +126 -118
  8. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/fastStub.py +13 -13
  9. fastsdk-0.4.2/fastsdk/requires.py +36 -0
  10. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/sdk_factory/sdk_factory.py +27 -25
  11. fastsdk-0.4.2/fastsdk/service_access.py +40 -0
  12. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/api_job_manager.py +8 -8
  13. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/api_seex.py +12 -12
  14. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/job_tasks.py +8 -8
  15. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/pipeline_planner.py +9 -8
  16. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/provider_factory.py +25 -30
  17. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/provider_stack_registry.py +12 -12
  18. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/request/api_client.py +20 -12
  19. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/request/api_client_replicate.py +5 -4
  20. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/request/api_client_runpod.py +6 -5
  21. fastsdk-0.4.2/fastsdk/service_interaction/request/api_client_socaity.py +150 -0
  22. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_specification_loader/replicate_loader.py +22 -16
  23. fastsdk-0.4.2/fastsdk/service_specification_loader/runpod_open_api_loader.py +68 -0
  24. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk.egg-info/PKG-INFO +5 -5
  25. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk.egg-info/SOURCES.txt +2 -0
  26. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk.egg-info/requires.txt +2 -2
  27. {fastsdk-0.3.1 → fastsdk-0.4.2}/pyproject.toml +3 -3
  28. {fastsdk-0.3.1 → fastsdk-0.4.2}/test/test_apipod_debug_test_services.py +23 -22
  29. fastsdk-0.4.2/test/test_apipod_registry_service_management_example.py +118 -0
  30. {fastsdk-0.3.1 → fastsdk-0.4.2}/test/test_client_factory.py +2 -2
  31. {fastsdk-0.3.1 → fastsdk-0.4.2}/test/test_client_factory_manual.py +21 -16
  32. {fastsdk-0.3.1 → fastsdk-0.4.2}/test/test_client_from_runpod_serverless.py +2 -3
  33. {fastsdk-0.3.1 → fastsdk-0.4.2}/test/test_localhost_services.py +2 -2
  34. {fastsdk-0.3.1 → fastsdk-0.4.2}/test/test_manual_sdk.py +2 -3
  35. {fastsdk-0.3.1 → fastsdk-0.4.2}/test/test_replicate.py +1 -1
  36. fastsdk-0.3.1/fastsdk/service_interaction/request/api_client_socaity.py +0 -79
  37. fastsdk-0.3.1/fastsdk/service_specification_loader/runpod_open_api_loader.py +0 -72
  38. fastsdk-0.3.1/test/test_apipod_registry_service_management_example.py +0 -161
  39. {fastsdk-0.3.1 → fastsdk-0.4.2}/LICENSE +0 -0
  40. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/sdk_factory/__init__.py +0 -0
  41. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/sdk_factory/sdk_template.j2 +0 -0
  42. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/__init__.py +0 -0
  43. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/async_bridge.py +0 -0
  44. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/job_runtime.py +0 -0
  45. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/request/__init__.py +0 -0
  46. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/request/file_handler.py +0 -0
  47. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/response/api_job_status.py +0 -0
  48. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/response/response_parser.py +0 -0
  49. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/response/response_schemas.py +0 -0
  50. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/response/sse_assembly.py +0 -0
  51. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_interaction/response/stream_session.py +0 -0
  52. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk/service_specification_loader/spec_loader.py +0 -0
  53. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk.egg-info/dependency_links.txt +0 -0
  54. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk.egg-info/entry_points.txt +0 -0
  55. {fastsdk-0.3.1 → fastsdk-0.4.2}/fastsdk.egg-info/top_level.txt +0 -0
  56. {fastsdk-0.3.1 → fastsdk-0.4.2}/setup.cfg +0 -0
  57. {fastsdk-0.3.1 → fastsdk-0.4.2}/test/test_client_from_fastapi.py +0 -0
  58. {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.1
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.0.2
695
- Requires-Dist: socaity-schemas>=0.0.1
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 definition** | The parsed, normalized description of a service: endpoints, parameters, address, provider type. Get one with `fastsdk.inspect_service(source)` — it has no side effects. |
815
- | **Registry** | An in-process directory of service definitions, shared by all clients. `register_service()` adds to it; generated stubs look their service up in it by ID. |
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 definition** | The parsed, normalized description of a service: endpoints, parameters, address, provider type. Get one with `fastsdk.inspect_service(source)` — it has no side effects. |
114
- | **Registry** | An in-process directory of service definitions, shared by all clients. `register_service()` adds to it; generated stubs look their service up in it by ID. |
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
 
@@ -10,7 +10,7 @@ from .api import (
10
10
  get_service,
11
11
  list_services,
12
12
  remove_service,
13
- ) # create_sdk is a deprecated alias of generate_stub
13
+ )
14
14
  from .service_interaction.api_seex import APISeex
15
15
  from .fastClient import FastClient
16
16
  from .fastSDK import FastSDK
@@ -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
- sd = fastsdk.inspect_service("replicate:owner/name") # look at a service without side effects
11
- sd = fastsdk.register_service("./openapi.json") # add a service to the registry
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.service_definitions import ServiceDefinition
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], ServiceDefinition],
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, ServiceDefinition,
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], ServiceDefinition],
51
+ source: Union[str, Path, Dict[str, Any], AIService],
52
52
  api_key: Optional[str] = None,
53
53
  **kwargs
54
- ) -> ServiceDefinition:
54
+ ) -> AIService:
55
55
  """
56
- Load and parse a service into a ServiceDefinition without registering it anywhere.
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 ServiceDefinition.
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 service_name, service_id, service_address, specification, ...
62
+ **kwargs: Overrides such as provider, service_id, name.
63
63
 
64
64
  Returns:
65
- ServiceDefinition with endpoints, parameters and the resolved service address.
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], ServiceDefinition],
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, ServiceDefinition, Replicate model
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
- GeneratedStub with .path, .class_name, .service_definition and .client().
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], ServiceDefinition],
104
+ source: Union[str, Path, Dict[str, Any], AIService],
104
105
  **kwargs
105
- ) -> ServiceDefinition:
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 ServiceDefinition.
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 ServiceDefinition.
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[ServiceDefinition]:
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[ServiceDefinition]:
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.service_definitions import ServiceDefinition
23
- from apipod_registry.service_registry.file_system_registry import FileSystemStore
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, ServiceDefinition]:
42
- """If the source matches a service in the persistent CLI registry, use that definition."""
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 _service_summary(sd: ServiceDefinition) -> str:
51
- address = getattr(sd.service_address, "url", None) or "-"
52
- return f"{sd.display_name} (id: {sd.id}, spec: {sd.specification or 'openapi'}, address: {address})"
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(sd: ServiceDefinition, as_json: bool = False):
65
+ def _print_service(service: AIService, as_json: bool = False):
56
66
  if as_json:
57
- print(json.dumps(sd.model_dump(exclude_none=True), indent=2, default=str))
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
- print(f"Service: {sd.display_name}")
63
- print(f"ID: {sd.id}")
64
- print(f"Spec: {sd.specification or 'openapi'}")
65
- print(f"Address: {getattr(sd.service_address, 'url', None) or '-'}")
66
- if sd.description:
67
- desc = sd.description.strip().split("\n")[0]
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 sd.endpoints:
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
- sd = fastsdk.inspect_service(_resolve_source(args.source), api_key=args.api_key)
154
- _print_service(sd, as_json=args.json)
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(sd)}")
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
- sd = client.service_definition
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(sd.endpoints) == 1:
190
- endpoint = sd.endpoints[0].path
201
+ if len(endpoints) == 1:
202
+ endpoint = endpoints[0].path
191
203
  else:
192
- paths = "\n ".join(ep.path for ep in sd.endpoints)
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 {sd.display_name} ...")
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 sd in services:
220
- print(f" {_service_summary(sd)}")
231
+ for service in services:
232
+ print(f" {_service_summary(service)}")
221
233
 
222
234
  elif args.registry_command == "add":
223
- sd = fastsdk.inspect_service(
235
+ service = fastsdk.inspect_service(
224
236
  args.source,
225
237
  service_id=args.id,
226
- service_name=args.name,
238
+ name=args.name,
227
239
  api_key=args.api_key
228
240
  )
229
- if sd.id and registry.get_service(sd.id):
230
- registry.remove_service(sd.id)
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
- sd = registry.get_service(args.id)
243
- if not sd:
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(sd, as_json=args.json)
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.service_definitions import ServiceDefinition, ReplicateServiceAddress, RunpodServiceAddress, SocaityServiceAddress
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 a ServiceDefinition
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], ServiceDefinition, None] = None,
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, a ServiceDefinition or a Replicate model reference.
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, ServiceDefinition) or a registered service ID/name.")
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
- # Strict lookup path used by generated stubs.
55
- self.service_definition = self.fsdk.service_registry.get_service(service_name_or_id)
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.service_definition = self._resolve_service(service, api_key, **load_kwargs)
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
- # Load the provider stack (client, file handler, parser) for this service.
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) -> ServiceDefinition:
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
- service_def = self.fsdk.register_service(service, api_key=api_key, update_existing=not self.temporary, **load_kwargs)
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
- # for global services
83
- if isinstance(self.service_definition.service_address, SocaityServiceAddress):
84
- return os.getenv("SOCAITY_API_KEY", None)
85
- elif isinstance(self.service_definition.service_address, RunpodServiceAddress):
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.service_definition.id, endpoint_id, data=kwargs)
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, 'service_definition', None) and hasattr(self, 'fsdk'):
112
+ if self.temporary and getattr(self, 'service', None) and hasattr(self, 'fsdk'):
99
113
  try:
100
- self.fsdk.service_registry.remove_service(self.service_definition.id)
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