servo-client 0.2.0__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.
- servo_client-0.2.0/MANIFEST.in +5 -0
- servo_client-0.2.0/PKG-INFO +146 -0
- servo_client-0.2.0/README.md +129 -0
- servo_client-0.2.0/pyproject.toml +56 -0
- servo_client-0.2.0/servo_client_build_backend.py +356 -0
- servo_client-0.2.0/setup.cfg +4 -0
- servo_client-0.2.0/src/servo/__init__.py +119 -0
- servo_client-0.2.0/src/servo/agent/__init__.py +13 -0
- servo_client-0.2.0/src/servo/agent/__main__.py +6 -0
- servo_client-0.2.0/src/servo/agent/cli.py +55 -0
- servo_client-0.2.0/src/servo/agent/daemon.py +1225 -0
- servo_client-0.2.0/src/servo/agent/protocol.py +279 -0
- servo_client-0.2.0/src/servo/agent/shim.py +356 -0
- servo_client-0.2.0/src/servo/cli.py +354 -0
- servo_client-0.2.0/src/servo/client.py +322 -0
- servo_client-0.2.0/src/servo/constants.py +88 -0
- servo_client-0.2.0/src/servo/direct.py +1211 -0
- servo_client-0.2.0/src/servo/errors.py +111 -0
- servo_client-0.2.0/src/servo/execution/__init__.py +1 -0
- servo_client-0.2.0/src/servo/execution/_gpu_jpeg_encode.py +538 -0
- servo_client-0.2.0/src/servo/execution/act_recording.py +582 -0
- servo_client-0.2.0/src/servo/execution/action_session_native_quic.py +951 -0
- servo_client-0.2.0/src/servo/execution/action_session_options.py +33 -0
- servo_client-0.2.0/src/servo/execution/action_session_quic.py +433 -0
- servo_client-0.2.0/src/servo/execution/action_session_transport/__init__.py +1946 -0
- servo_client-0.2.0/src/servo/execution/action_session_transport/_channel_types.py +99 -0
- servo_client-0.2.0/src/servo/execution/action_session_transport/_channels.py +1025 -0
- servo_client-0.2.0/src/servo/execution/action_session_transport/_control_verbs.py +321 -0
- servo_client-0.2.0/src/servo/execution/action_session_transport/_livekit_channel.py +487 -0
- servo_client-0.2.0/src/servo/execution/action_session_transport/_recording.py +151 -0
- servo_client-0.2.0/src/servo/execution/action_session_transport/_recovery.py +73 -0
- servo_client-0.2.0/src/servo/execution/action_session_transport/_resumption.py +223 -0
- servo_client-0.2.0/src/servo/execution/adaptive_chunking.py +183 -0
- servo_client-0.2.0/src/servo/execution/cadence.py +950 -0
- servo_client-0.2.0/src/servo/execution/chunk_scheduler.py +885 -0
- servo_client-0.2.0/src/servo/execution/chunked_loop.py +1041 -0
- servo_client-0.2.0/src/servo/execution/chunked_transport.py +154 -0
- servo_client-0.2.0/src/servo/execution/encoded_observation.py +1274 -0
- servo_client-0.2.0/src/servo/execution/gripper_channels.py +75 -0
- servo_client-0.2.0/src/servo/execution/h264_observation.py +853 -0
- servo_client-0.2.0/src/servo/execution/multistep_solvers.py +43 -0
- servo_client-0.2.0/src/servo/execution/nat_punch.py +326 -0
- servo_client-0.2.0/src/servo/execution/observation_encoders.py +748 -0
- servo_client-0.2.0/src/servo/execution/policy.py +298 -0
- servo_client-0.2.0/src/servo/execution/robot_agent.py +51 -0
- servo_client-0.2.0/src/servo/execution/robot_agent_protocol.py +86 -0
- servo_client-0.2.0/src/servo/execution/session.py +460 -0
- servo_client-0.2.0/src/servo/execution/sfa_observation.py +244 -0
- servo_client-0.2.0/src/servo/execution/speculative_action_decode.py +201 -0
- servo_client-0.2.0/src/servo/execution/split_expert.py +433 -0
- servo_client-0.2.0/src/servo/execution/trajectory_blender.py +479 -0
- servo_client-0.2.0/src/servo/execution/world.py +76 -0
- servo_client-0.2.0/src/servo/hardware.py +1287 -0
- servo_client-0.2.0/src/servo/namespaces/__init__.py +1 -0
- servo_client-0.2.0/src/servo/namespaces/adapters.py +26 -0
- servo_client-0.2.0/src/servo/namespaces/datasets.py +214 -0
- servo_client-0.2.0/src/servo/namespaces/deployments.py +22 -0
- servo_client-0.2.0/src/servo/namespaces/jobs.py +109 -0
- servo_client-0.2.0/src/servo/namespaces/licenses.py +23 -0
- servo_client-0.2.0/src/servo/namespaces/models.py +464 -0
- servo_client-0.2.0/src/servo/namespaces/robots.py +104 -0
- servo_client-0.2.0/src/servo/namespaces/secrets.py +43 -0
- servo_client-0.2.0/src/servo/namespaces/self_hosted.py +55 -0
- servo_client-0.2.0/src/servo/public_api.py +139 -0
- servo_client-0.2.0/src/servo/resources/__init__.py +1 -0
- servo_client-0.2.0/src/servo/resources/adapter.py +79 -0
- servo_client-0.2.0/src/servo/resources/base.py +159 -0
- servo_client-0.2.0/src/servo/resources/dataset.py +514 -0
- servo_client-0.2.0/src/servo/resources/deployment.py +329 -0
- servo_client-0.2.0/src/servo/resources/embodiment.py +41 -0
- servo_client-0.2.0/src/servo/resources/generation.py +110 -0
- servo_client-0.2.0/src/servo/resources/hf_import.py +317 -0
- servo_client-0.2.0/src/servo/resources/job.py +224 -0
- servo_client-0.2.0/src/servo/resources/model.py +383 -0
- servo_client-0.2.0/src/servo/resources/secret.py +38 -0
- servo_client-0.2.0/src/servo/resources/self_hosted.py +133 -0
- servo_client-0.2.0/src/servo/resources/upload.py +35 -0
- servo_client-0.2.0/src/servo/resources/usage.py +100 -0
- servo_client-0.2.0/src/servo/templates.py +51 -0
- servo_client-0.2.0/src/servo/types.py +161 -0
- servo_client-0.2.0/src/servo_action_session/__init__.py +24 -0
- servo_client-0.2.0/src/servo_action_session/effects.py +33 -0
- servo_client-0.2.0/src/servo_action_session/events.py +53 -0
- servo_client-0.2.0/src/servo_action_session/reducer.py +783 -0
- servo_client-0.2.0/src/servo_action_session/risk_switching.py +794 -0
- servo_client-0.2.0/src/servo_action_session/state.py +168 -0
- servo_client-0.2.0/src/servo_client.egg-info/PKG-INFO +146 -0
- servo_client-0.2.0/src/servo_client.egg-info/SOURCES.txt +164 -0
- servo_client-0.2.0/src/servo_client.egg-info/dependency_links.txt +1 -0
- servo_client-0.2.0/src/servo_client.egg-info/entry_points.txt +2 -0
- servo_client-0.2.0/src/servo_client.egg-info/requires.txt +10 -0
- servo_client-0.2.0/src/servo_client.egg-info/top_level.txt +6 -0
- servo_client-0.2.0/src/servo_contracts/__init__.py +189 -0
- servo_client-0.2.0/src/servo_contracts/_compat.py +53 -0
- servo_client-0.2.0/src/servo_contracts/action_policy.py +102 -0
- servo_client-0.2.0/src/servo_contracts/action_session.py +2001 -0
- servo_client-0.2.0/src/servo_contracts/action_session_lifecycle.py +451 -0
- servo_client-0.2.0/src/servo_contracts/action_session_quic.py +190 -0
- servo_client-0.2.0/src/servo_contracts/action_session_v4.py +1805 -0
- servo_client-0.2.0/src/servo_contracts/action_session_v4_pb2.py +129 -0
- servo_client-0.2.0/src/servo_contracts/action_session_wire.py +140 -0
- servo_client-0.2.0/src/servo_contracts/action_stream.py +147 -0
- servo_client-0.2.0/src/servo_contracts/artifacts.py +62 -0
- servo_client-0.2.0/src/servo_contracts/benchmark.py +258 -0
- servo_client-0.2.0/src/servo_contracts/compiled_execution.py +915 -0
- servo_client-0.2.0/src/servo_contracts/configuration.py +91 -0
- servo_client-0.2.0/src/servo_contracts/content_identity.py +55 -0
- servo_client-0.2.0/src/servo_contracts/environment.py +253 -0
- servo_client-0.2.0/src/servo_contracts/errors.py +296 -0
- servo_client-0.2.0/src/servo_contracts/execution_evidence.py +117 -0
- servo_client-0.2.0/src/servo_contracts/git_identity.py +140 -0
- servo_client-0.2.0/src/servo_contracts/hf_tasks.py +169 -0
- servo_client-0.2.0/src/servo_contracts/inference.py +207 -0
- servo_client-0.2.0/src/servo_contracts/observability.py +365 -0
- servo_client-0.2.0/src/servo_contracts/observation.py +330 -0
- servo_client-0.2.0/src/servo_contracts/onboarding.py +656 -0
- servo_client-0.2.0/src/servo_contracts/openpi.py +120 -0
- servo_client-0.2.0/src/servo_contracts/optimization/__init__.py +108 -0
- servo_client-0.2.0/src/servo_contracts/optimization/base.py +59 -0
- servo_client-0.2.0/src/servo_contracts/optimization/comparison.py +689 -0
- servo_client-0.2.0/src/servo_contracts/optimization/enums.py +130 -0
- servo_client-0.2.0/src/servo_contracts/optimization/execution.py +740 -0
- servo_client-0.2.0/src/servo_contracts/optimization/hardware.py +257 -0
- servo_client-0.2.0/src/servo_contracts/optimization/search.py +380 -0
- servo_client-0.2.0/src/servo_contracts/qualification.py +48 -0
- servo_client-0.2.0/src/servo_contracts/release.py +188 -0
- servo_client-0.2.0/src/servo_contracts/repository.py +138 -0
- servo_client-0.2.0/src/servo_contracts/resources.py +155 -0
- servo_client-0.2.0/src/servo_contracts/risk_switching.py +425 -0
- servo_client-0.2.0/src/servo_contracts/robot_agent_v1_pb2.py +55 -0
- servo_client-0.2.0/src/servo_contracts/robot_control.py +187 -0
- servo_client-0.2.0/src/servo_contracts/runtime.py +350 -0
- servo_client-0.2.0/src/servo_contracts/self_hosted.py +329 -0
- servo_client-0.2.0/src/servo_contracts/serving.py +566 -0
- servo_client-0.2.0/src/servo_contracts/solvers.py +569 -0
- servo_client-0.2.0/src/servo_contracts/templates.py +273 -0
- servo_client-0.2.0/src/servo_data/__init__.py +5 -0
- servo_client-0.2.0/src/servo_data/streaming.py +735 -0
- servo_client-0.2.0/src/servo_observation/__init__.py +86 -0
- servo_client-0.2.0/src/servo_observation/clocks.py +47 -0
- servo_client-0.2.0/src/servo_observation/errors.py +103 -0
- servo_client-0.2.0/src/servo_observation/frames.py +386 -0
- servo_client-0.2.0/src/servo_observation/identity.py +126 -0
- servo_client-0.2.0/src/servo_observation/pipeline.py +366 -0
- servo_client-0.2.0/src/servo_observation/routes.py +207 -0
- servo_client-0.2.0/src/servo_telemetry/__init__.py +48 -0
- servo_client-0.2.0/src/servo_telemetry/api.py +285 -0
- servo_client-0.2.0/src/servo_telemetry/bootstrap.py +95 -0
- servo_client-0.2.0/src/servo_telemetry/boundaries.py +219 -0
- servo_client-0.2.0/src/servo_telemetry/catalog.py +664 -0
- servo_client-0.2.0/src/servo_telemetry/config.py +161 -0
- servo_client-0.2.0/src/servo_telemetry/context.py +60 -0
- servo_client-0.2.0/src/servo_telemetry/contracts.py +671 -0
- servo_client-0.2.0/src/servo_telemetry/declarations.py +229 -0
- servo_client-0.2.0/src/servo_telemetry/discovery.py +133 -0
- servo_client-0.2.0/src/servo_telemetry/dispatch.py +220 -0
- servo_client-0.2.0/src/servo_telemetry/model.py +166 -0
- servo_client-0.2.0/src/servo_telemetry/nvtx.py +183 -0
- servo_client-0.2.0/src/servo_telemetry/otlp_http.py +31 -0
- servo_client-0.2.0/src/servo_telemetry/profiling.py +82 -0
- servo_client-0.2.0/src/servo_telemetry/resource.py +321 -0
- servo_client-0.2.0/src/servo_telemetry/sinks/__init__.py +1 -0
- servo_client-0.2.0/src/servo_telemetry/sinks/capture.py +33 -0
- servo_client-0.2.0/src/servo_telemetry/sinks/metrics.py +197 -0
- servo_client-0.2.0/src/servo_telemetry/sinks/noop.py +17 -0
- servo_client-0.2.0/src/servo_telemetry/sinks/otel.py +233 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: servo-client
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Python SDK for Servo's hosted robot model API
|
|
5
|
+
Project-URL: Documentation, https://servo.mintlify.site/
|
|
6
|
+
Requires-Python: >=3.12
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: cryptography>=42
|
|
9
|
+
Requires-Dist: httpx[http2]
|
|
10
|
+
Requires-Dist: msgpack>=1.0.5
|
|
11
|
+
Requires-Dist: numpy>=1.26
|
|
12
|
+
Requires-Dist: pydantic>=2
|
|
13
|
+
Requires-Dist: protobuf<7,>=6.33.6
|
|
14
|
+
Requires-Dist: websockets>=13
|
|
15
|
+
Provides-Extra: livekit
|
|
16
|
+
Requires-Dist: livekit==1.1.14; extra == "livekit"
|
|
17
|
+
|
|
18
|
+
# servo-client
|
|
19
|
+
|
|
20
|
+
The public Python SDK and customer CLI for Servo robot policies, world models, datasets, jobs, and
|
|
21
|
+
deployments.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install 'servo-client>=0.2,<0.3'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Servo releases two architecture-specific Linux wheels containing the native Quinn action-session
|
|
30
|
+
binary, one portable wheel, and a source distribution. Native clients prefer a validated QUIC
|
|
31
|
+
route; WSS and H2 binary remain ordered pre-inference fallbacks. Portable installations use the
|
|
32
|
+
advertised non-native routes. Every route carries the same ActionSession v4 Protobuf frames.
|
|
33
|
+
|
|
34
|
+
LiveKit is an explicit optional transport:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install 'servo-client[livekit]>=0.2,<0.3'
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Configure the control plane
|
|
41
|
+
|
|
42
|
+
Servo has no compiled-in hosted URL. Set the origin supplied by your Servo administrator so an old
|
|
43
|
+
package cannot silently send credentials or robot traffic to a retired deployment:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
export SERVO_BASE_URL='https://<your-servo-control-plane>'
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The client also accepts `base_url=` explicitly. The argument takes precedence over the environment.
|
|
50
|
+
|
|
51
|
+
## Sign in
|
|
52
|
+
|
|
53
|
+
The distribution installs the customer-safe `servo` CLI:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
servo login
|
|
57
|
+
servo whoami
|
|
58
|
+
servo key create --label laptop
|
|
59
|
+
servo deployment list
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Serving, benchmarking, profiling, and platform-operator commands belong to the private
|
|
63
|
+
`servo-platform` distribution and are not installed by `servo-client`.
|
|
64
|
+
|
|
65
|
+
## Quickstart
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
import os
|
|
69
|
+
|
|
70
|
+
import servo
|
|
71
|
+
from servo import templates
|
|
72
|
+
|
|
73
|
+
sv = servo.Servo(
|
|
74
|
+
base_url=os.environ["SERVO_BASE_URL"],
|
|
75
|
+
api_key=os.environ["SERVO_API_KEY"],
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
robot = sv.robots.register(templates.so101())
|
|
79
|
+
observation = templates.sample_observation(robot)
|
|
80
|
+
model = sv.models.get("smolvla")
|
|
81
|
+
|
|
82
|
+
report = model.inspect(robot)
|
|
83
|
+
if not report.compatible:
|
|
84
|
+
raise RuntimeError(report.issues)
|
|
85
|
+
|
|
86
|
+
prediction = model.policy(robot, instruction="pick up the cube").act(observation)
|
|
87
|
+
print(prediction.actions[0])
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Action sessions
|
|
91
|
+
|
|
92
|
+
`sv.session(policy)` negotiates the transports advertised by the selected deployment. Once an
|
|
93
|
+
inference request could have executed, Servo never replays it over another route.
|
|
94
|
+
|
|
95
|
+
LiveKit requires both deployment and session opt-in:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
provider = servo.ServingProvider(os.environ["SERVO_SERVE_PROVIDER"])
|
|
99
|
+
deployment = model.deploy(
|
|
100
|
+
robot,
|
|
101
|
+
serve_provider=provider,
|
|
102
|
+
action_session_channels=("livekit", "wss"),
|
|
103
|
+
)
|
|
104
|
+
policy = deployment.policy(robot, instruction="pick up the cube")
|
|
105
|
+
|
|
106
|
+
with sv.session(
|
|
107
|
+
policy,
|
|
108
|
+
action_session=servo.ActionSessionOptions(channels=("livekit",)),
|
|
109
|
+
) as session:
|
|
110
|
+
prediction = session.act(observation)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Public boundary
|
|
114
|
+
|
|
115
|
+
Application compatibility is promised for documented imports from `servo` and these client
|
|
116
|
+
namespaces:
|
|
117
|
+
|
|
118
|
+
- Stable: `robots`, `adapters`, `models`, `datasets`/`data`, `deployments`, `jobs`, `licenses`, and
|
|
119
|
+
`secrets`.
|
|
120
|
+
- Advanced: direct endpoint helpers (`servo.connect`, `servo.attach`, `EndpointGrant`) and
|
|
121
|
+
`sv.self_hosted`.
|
|
122
|
+
|
|
123
|
+
The distribution also carries dependency-light support packages:
|
|
124
|
+
|
|
125
|
+
- Extension contracts: `servo_action_session`, `servo_data`, and `servo_observation`.
|
|
126
|
+
- Implementation dependencies: `servo_contracts` and `servo_telemetry`.
|
|
127
|
+
|
|
128
|
+
Importability of an implementation package is not a customer compatibility promise. The client
|
|
129
|
+
wheel excludes product-only profiler integration and does not contain `servo_platform`,
|
|
130
|
+
`servo_runtimes`, FastAPI routes, database models, provider launchers, provider credentials, or
|
|
131
|
+
private endpoint URLs.
|
|
132
|
+
|
|
133
|
+
## Failure and retry policy
|
|
134
|
+
|
|
135
|
+
The client does not automatically replay mutations or robot actions. Transport and server failures
|
|
136
|
+
are returned as typed Servo errors so applications can choose a recovery policy appropriate to the
|
|
137
|
+
operation. Idempotent reads may be retried by application code. Configure `timeout=` on
|
|
138
|
+
`servo.Servo`; long-running synchronous jobs can require a larger read timeout than catalog calls.
|
|
139
|
+
|
|
140
|
+
## Requirements
|
|
141
|
+
|
|
142
|
+
- Python 3.12+
|
|
143
|
+
- A Servo control-plane URL
|
|
144
|
+
- An organization API key for authenticated operations
|
|
145
|
+
|
|
146
|
+
Full documentation: <https://servo.mintlify.site/>
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# servo-client
|
|
2
|
+
|
|
3
|
+
The public Python SDK and customer CLI for Servo robot policies, world models, datasets, jobs, and
|
|
4
|
+
deployments.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install 'servo-client>=0.2,<0.3'
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Servo releases two architecture-specific Linux wheels containing the native Quinn action-session
|
|
13
|
+
binary, one portable wheel, and a source distribution. Native clients prefer a validated QUIC
|
|
14
|
+
route; WSS and H2 binary remain ordered pre-inference fallbacks. Portable installations use the
|
|
15
|
+
advertised non-native routes. Every route carries the same ActionSession v4 Protobuf frames.
|
|
16
|
+
|
|
17
|
+
LiveKit is an explicit optional transport:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install 'servo-client[livekit]>=0.2,<0.3'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Configure the control plane
|
|
24
|
+
|
|
25
|
+
Servo has no compiled-in hosted URL. Set the origin supplied by your Servo administrator so an old
|
|
26
|
+
package cannot silently send credentials or robot traffic to a retired deployment:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
export SERVO_BASE_URL='https://<your-servo-control-plane>'
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The client also accepts `base_url=` explicitly. The argument takes precedence over the environment.
|
|
33
|
+
|
|
34
|
+
## Sign in
|
|
35
|
+
|
|
36
|
+
The distribution installs the customer-safe `servo` CLI:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
servo login
|
|
40
|
+
servo whoami
|
|
41
|
+
servo key create --label laptop
|
|
42
|
+
servo deployment list
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Serving, benchmarking, profiling, and platform-operator commands belong to the private
|
|
46
|
+
`servo-platform` distribution and are not installed by `servo-client`.
|
|
47
|
+
|
|
48
|
+
## Quickstart
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import os
|
|
52
|
+
|
|
53
|
+
import servo
|
|
54
|
+
from servo import templates
|
|
55
|
+
|
|
56
|
+
sv = servo.Servo(
|
|
57
|
+
base_url=os.environ["SERVO_BASE_URL"],
|
|
58
|
+
api_key=os.environ["SERVO_API_KEY"],
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
robot = sv.robots.register(templates.so101())
|
|
62
|
+
observation = templates.sample_observation(robot)
|
|
63
|
+
model = sv.models.get("smolvla")
|
|
64
|
+
|
|
65
|
+
report = model.inspect(robot)
|
|
66
|
+
if not report.compatible:
|
|
67
|
+
raise RuntimeError(report.issues)
|
|
68
|
+
|
|
69
|
+
prediction = model.policy(robot, instruction="pick up the cube").act(observation)
|
|
70
|
+
print(prediction.actions[0])
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Action sessions
|
|
74
|
+
|
|
75
|
+
`sv.session(policy)` negotiates the transports advertised by the selected deployment. Once an
|
|
76
|
+
inference request could have executed, Servo never replays it over another route.
|
|
77
|
+
|
|
78
|
+
LiveKit requires both deployment and session opt-in:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
provider = servo.ServingProvider(os.environ["SERVO_SERVE_PROVIDER"])
|
|
82
|
+
deployment = model.deploy(
|
|
83
|
+
robot,
|
|
84
|
+
serve_provider=provider,
|
|
85
|
+
action_session_channels=("livekit", "wss"),
|
|
86
|
+
)
|
|
87
|
+
policy = deployment.policy(robot, instruction="pick up the cube")
|
|
88
|
+
|
|
89
|
+
with sv.session(
|
|
90
|
+
policy,
|
|
91
|
+
action_session=servo.ActionSessionOptions(channels=("livekit",)),
|
|
92
|
+
) as session:
|
|
93
|
+
prediction = session.act(observation)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Public boundary
|
|
97
|
+
|
|
98
|
+
Application compatibility is promised for documented imports from `servo` and these client
|
|
99
|
+
namespaces:
|
|
100
|
+
|
|
101
|
+
- Stable: `robots`, `adapters`, `models`, `datasets`/`data`, `deployments`, `jobs`, `licenses`, and
|
|
102
|
+
`secrets`.
|
|
103
|
+
- Advanced: direct endpoint helpers (`servo.connect`, `servo.attach`, `EndpointGrant`) and
|
|
104
|
+
`sv.self_hosted`.
|
|
105
|
+
|
|
106
|
+
The distribution also carries dependency-light support packages:
|
|
107
|
+
|
|
108
|
+
- Extension contracts: `servo_action_session`, `servo_data`, and `servo_observation`.
|
|
109
|
+
- Implementation dependencies: `servo_contracts` and `servo_telemetry`.
|
|
110
|
+
|
|
111
|
+
Importability of an implementation package is not a customer compatibility promise. The client
|
|
112
|
+
wheel excludes product-only profiler integration and does not contain `servo_platform`,
|
|
113
|
+
`servo_runtimes`, FastAPI routes, database models, provider launchers, provider credentials, or
|
|
114
|
+
private endpoint URLs.
|
|
115
|
+
|
|
116
|
+
## Failure and retry policy
|
|
117
|
+
|
|
118
|
+
The client does not automatically replay mutations or robot actions. Transport and server failures
|
|
119
|
+
are returned as typed Servo errors so applications can choose a recovery policy appropriate to the
|
|
120
|
+
operation. Idempotent reads may be retried by application code. Configure `timeout=` on
|
|
121
|
+
`servo.Servo`; long-running synchronous jobs can require a larger read timeout than catalog calls.
|
|
122
|
+
|
|
123
|
+
## Requirements
|
|
124
|
+
|
|
125
|
+
- Python 3.12+
|
|
126
|
+
- A Servo control-plane URL
|
|
127
|
+
- An organization API key for authenticated operations
|
|
128
|
+
|
|
129
|
+
Full documentation: <https://servo.mintlify.site/>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "servo_client_build_backend"
|
|
4
|
+
backend-path = ["."]
|
|
5
|
+
|
|
6
|
+
[project]
|
|
7
|
+
name = "servo-client"
|
|
8
|
+
version = "0.2.0"
|
|
9
|
+
description = "Python SDK for Servo's hosted robot model API"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
requires-python = ">=3.12"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"cryptography>=42",
|
|
14
|
+
"httpx[http2]",
|
|
15
|
+
"msgpack>=1.0.5",
|
|
16
|
+
"numpy>=1.26",
|
|
17
|
+
"pydantic>=2",
|
|
18
|
+
"protobuf>=6.33.6,<7",
|
|
19
|
+
"websockets>=13",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.optional-dependencies]
|
|
23
|
+
livekit = ["livekit==1.1.14"]
|
|
24
|
+
|
|
25
|
+
[project.scripts]
|
|
26
|
+
servo = "servo.cli:main"
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Documentation = "https://servo.mintlify.site/"
|
|
30
|
+
|
|
31
|
+
[tool.setuptools]
|
|
32
|
+
package-dir = {"" = "src"}
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
where = ["src"]
|
|
36
|
+
include = [
|
|
37
|
+
"servo",
|
|
38
|
+
"servo.*",
|
|
39
|
+
"servo_action_session",
|
|
40
|
+
"servo_action_session.*",
|
|
41
|
+
"servo_contracts",
|
|
42
|
+
"servo_contracts.*",
|
|
43
|
+
"servo_data",
|
|
44
|
+
"servo_data.*",
|
|
45
|
+
"servo_observation",
|
|
46
|
+
"servo_observation.*",
|
|
47
|
+
"servo_telemetry",
|
|
48
|
+
"servo_telemetry.*",
|
|
49
|
+
]
|
|
50
|
+
exclude = ["servo_platform*"]
|
|
51
|
+
|
|
52
|
+
# The native QUIC data plane is the default transport, so the compiled artifact
|
|
53
|
+
# ships with the client rather than being something the user has to locate or
|
|
54
|
+
# build. Wheels carrying it are platform-specific by construction.
|
|
55
|
+
[tool.setuptools.package-data]
|
|
56
|
+
servo = ["_native/servo-action-session-quic"]
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
"""Build the public client from the monorepo's canonical Python sources.
|
|
2
|
+
|
|
3
|
+
The public distribution is a strict projection of selected packages in the repository-level
|
|
4
|
+
``src`` tree. A PEP 517 source distribution must be self-contained, so pointing setuptools at
|
|
5
|
+
``../../src`` produces an sdist whose paths escape the archive and whose wheel cannot be rebuilt.
|
|
6
|
+
This backend stages only the public packages into the conventional local ``src`` layout before
|
|
7
|
+
delegating to setuptools. The generated sdist therefore contains its complete, immutable build
|
|
8
|
+
input and can be built independently of the Servo repository.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import ast
|
|
14
|
+
import os
|
|
15
|
+
import shutil
|
|
16
|
+
import tempfile
|
|
17
|
+
from collections.abc import Iterator, Mapping
|
|
18
|
+
from contextlib import contextmanager
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from setuptools import build_meta as _setuptools
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
_PROJECT_ROOT = Path(__file__).resolve().parent
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _contract_value(name: str) -> tuple[str, ...]:
|
|
29
|
+
"""Read a tuple from the data-only SDK contract without importing ``servo``."""
|
|
30
|
+
|
|
31
|
+
candidates = (
|
|
32
|
+
_PROJECT_ROOT / "src" / "servo" / "public_api.py",
|
|
33
|
+
(_PROJECT_ROOT / ".." / ".." / "src" / "servo" / "public_api.py").resolve(),
|
|
34
|
+
)
|
|
35
|
+
contract_path = next((path for path in candidates if path.is_file()), None)
|
|
36
|
+
if contract_path is None:
|
|
37
|
+
raise RuntimeError("servo-client build could not locate src/servo/public_api.py")
|
|
38
|
+
tree = ast.parse(contract_path.read_text(encoding="utf-8"), filename=str(contract_path))
|
|
39
|
+
values: dict[str, tuple[str, ...]] = {}
|
|
40
|
+
|
|
41
|
+
def evaluate(node: ast.expr) -> tuple[str, ...] | None:
|
|
42
|
+
if isinstance(node, ast.Name):
|
|
43
|
+
return values.get(node.id)
|
|
44
|
+
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.Add):
|
|
45
|
+
left = evaluate(node.left)
|
|
46
|
+
right = evaluate(node.right)
|
|
47
|
+
return left + right if left is not None and right is not None else None
|
|
48
|
+
try:
|
|
49
|
+
value = ast.literal_eval(node)
|
|
50
|
+
except (ValueError, TypeError):
|
|
51
|
+
return None
|
|
52
|
+
if isinstance(value, tuple) and all(isinstance(item, str) for item in value):
|
|
53
|
+
return value
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
for node in tree.body:
|
|
57
|
+
if not isinstance(node, ast.Assign) or len(node.targets) != 1:
|
|
58
|
+
continue
|
|
59
|
+
target = node.targets[0]
|
|
60
|
+
if not isinstance(target, ast.Name):
|
|
61
|
+
continue
|
|
62
|
+
value = evaluate(node.value)
|
|
63
|
+
if value is not None:
|
|
64
|
+
values[target.id] = value
|
|
65
|
+
try:
|
|
66
|
+
return values[name]
|
|
67
|
+
except KeyError as exc:
|
|
68
|
+
raise RuntimeError(f"servo-client public contract does not define {name}") from exc
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
_PUBLIC_PACKAGES = _contract_value("DISTRIBUTION_PACKAGES")
|
|
72
|
+
_CLIENT_EXCLUDED_PATHS = {
|
|
73
|
+
Path(*module.split(".")).with_suffix(".py")
|
|
74
|
+
for module in _contract_value("CLIENT_EXCLUDED_MODULES")
|
|
75
|
+
}
|
|
76
|
+
_STAGE_EXCLUDES = {
|
|
77
|
+
".git",
|
|
78
|
+
".pytest_cache",
|
|
79
|
+
".ruff_cache",
|
|
80
|
+
".venv",
|
|
81
|
+
"__pycache__",
|
|
82
|
+
"build",
|
|
83
|
+
"dist",
|
|
84
|
+
"src",
|
|
85
|
+
}
|
|
86
|
+
_PUBLIC_BUILD_ARTIFACT_SUFFIXES = {".dylib", ".pyd", ".so"}
|
|
87
|
+
_BENCHMARK_ONLY_SOURCES = {
|
|
88
|
+
"flatbuffers_cython.c",
|
|
89
|
+
"flatbuffers_cython.pyx",
|
|
90
|
+
}
|
|
91
|
+
_PLATFORM_TAG_ENV_VAR = "SERVO_CLIENT_WHEEL_PLATFORM_TAG"
|
|
92
|
+
_RELEASE_PLATFORM_TAGS = {
|
|
93
|
+
"manylinux_2_28_aarch64",
|
|
94
|
+
"manylinux_2_28_x86_64",
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _canonical_source_root() -> Path:
|
|
99
|
+
local_source = _PROJECT_ROOT / "src"
|
|
100
|
+
repository_source = (_PROJECT_ROOT / ".." / ".." / "src").resolve()
|
|
101
|
+
for candidate in (local_source, repository_source):
|
|
102
|
+
if all((candidate / package).is_dir() for package in _PUBLIC_PACKAGES):
|
|
103
|
+
return candidate
|
|
104
|
+
raise RuntimeError(
|
|
105
|
+
"servo-client build requires all configured public source packages: "
|
|
106
|
+
+ ", ".join(_PUBLIC_PACKAGES)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _ignore_stage_entry(path: Path) -> bool:
|
|
111
|
+
return (
|
|
112
|
+
path.name in _STAGE_EXCLUDES
|
|
113
|
+
or path.name.endswith(".egg-info")
|
|
114
|
+
or path.suffix in {".pyc", ".pyo"}
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _ignore_public_source_entry(path: Path, *, source_root: Path) -> bool:
|
|
119
|
+
"""Exclude local/benchmark build products from the public SDK projection.
|
|
120
|
+
|
|
121
|
+
The canonical source tree is also the output directory for optional native builds. Copying it
|
|
122
|
+
wholesale can otherwise put a developer's architecture-specific extension into a supposedly
|
|
123
|
+
portable wheel. The FlatBuffers Cython source is benchmark-only and is not a product codec.
|
|
124
|
+
The one native executable that is part of the product is suffixless and remains included.
|
|
125
|
+
"""
|
|
126
|
+
return (
|
|
127
|
+
_ignore_stage_entry(path)
|
|
128
|
+
or path.relative_to(source_root) in _CLIENT_EXCLUDED_PATHS
|
|
129
|
+
or path.suffix in _PUBLIC_BUILD_ARTIFACT_SUFFIXES
|
|
130
|
+
or path.name in _BENCHMARK_ONLY_SOURCES
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _copy_project(stage: Path) -> None:
|
|
135
|
+
stage.mkdir(parents=True)
|
|
136
|
+
for source in _PROJECT_ROOT.iterdir():
|
|
137
|
+
if _ignore_stage_entry(source):
|
|
138
|
+
continue
|
|
139
|
+
destination = stage / source.name
|
|
140
|
+
if source.is_dir():
|
|
141
|
+
shutil.copytree(
|
|
142
|
+
source,
|
|
143
|
+
destination,
|
|
144
|
+
ignore=lambda _directory, names: [
|
|
145
|
+
name for name in names if _ignore_stage_entry(Path(name))
|
|
146
|
+
],
|
|
147
|
+
)
|
|
148
|
+
else:
|
|
149
|
+
shutil.copy2(source, destination)
|
|
150
|
+
|
|
151
|
+
canonical_source = _canonical_source_root()
|
|
152
|
+
staged_source = stage / "src"
|
|
153
|
+
staged_source.mkdir()
|
|
154
|
+
for package in _PUBLIC_PACKAGES:
|
|
155
|
+
shutil.copytree(
|
|
156
|
+
canonical_source / package,
|
|
157
|
+
staged_source / package,
|
|
158
|
+
ignore=lambda directory, names: [
|
|
159
|
+
name
|
|
160
|
+
for name in names
|
|
161
|
+
if _ignore_public_source_entry(
|
|
162
|
+
Path(directory) / name,
|
|
163
|
+
source_root=canonical_source,
|
|
164
|
+
)
|
|
165
|
+
],
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _native_artifact(stage: Path) -> Path:
|
|
170
|
+
return stage / "src" / "servo" / "_native" / "servo-action-session-quic"
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _exclude_native_artifact(stage: Path) -> None:
|
|
174
|
+
"""Keep the host architecture's executable out of source distributions."""
|
|
175
|
+
artifact = _native_artifact(stage)
|
|
176
|
+
if artifact.is_file():
|
|
177
|
+
artifact.unlink()
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _configure_platform_wheel(stage: Path) -> None:
|
|
181
|
+
"""Make a wheel carrying the native data plane fail on another architecture."""
|
|
182
|
+
if not _native_artifact(stage).is_file():
|
|
183
|
+
return
|
|
184
|
+
platform_override = os.environ.get(_PLATFORM_TAG_ENV_VAR)
|
|
185
|
+
if platform_override is not None and platform_override not in _RELEASE_PLATFORM_TAGS:
|
|
186
|
+
raise RuntimeError(
|
|
187
|
+
f"{_PLATFORM_TAG_ENV_VAR} must name a governed release platform, "
|
|
188
|
+
f"got {platform_override!r}"
|
|
189
|
+
)
|
|
190
|
+
(stage / "setup.py").write_text(
|
|
191
|
+
f"""from setuptools import Distribution, setup
|
|
192
|
+
from wheel.bdist_wheel import bdist_wheel
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
PLATFORM_OVERRIDE = {platform_override!r}
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class PlatformDistribution(Distribution):
|
|
199
|
+
def has_ext_modules(self):
|
|
200
|
+
return True
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class PlatformWheel(bdist_wheel):
|
|
204
|
+
def finalize_options(self):
|
|
205
|
+
super().finalize_options()
|
|
206
|
+
self.root_is_pure = False
|
|
207
|
+
|
|
208
|
+
def get_tag(self):
|
|
209
|
+
_python, _abi, platform = super().get_tag()
|
|
210
|
+
return "py3", "none", PLATFORM_OVERRIDE or platform
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
setup(distclass=PlatformDistribution, cmdclass={{"bdist_wheel": PlatformWheel}})
|
|
214
|
+
""",
|
|
215
|
+
encoding="utf-8",
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _configure_editable_project(stage: Path) -> None:
|
|
220
|
+
"""Force setuptools' selective finder while retaining canonical source paths.
|
|
221
|
+
|
|
222
|
+
A single ``{"": "src"}`` mapping makes setuptools emit a plain ``.pth`` entry. Besides
|
|
223
|
+
pointing at the temporary staging tree, that would expose every repository package. Explicit
|
|
224
|
+
top-level mappings make setuptools emit its selective meta-path finder, and the finder resolves
|
|
225
|
+
directly to the canonical source directories after the build staging tree is removed.
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
canonical_source = _canonical_source_root()
|
|
229
|
+
mappings = ", ".join(
|
|
230
|
+
f'"{package}" = "{Path(os.path.relpath(canonical_source / package, stage)).as_posix()}"'
|
|
231
|
+
for package in _PUBLIC_PACKAGES
|
|
232
|
+
)
|
|
233
|
+
pyproject_path = stage / "pyproject.toml"
|
|
234
|
+
pyproject = pyproject_path.read_text(encoding="utf-8")
|
|
235
|
+
original = 'package-dir = {"" = "src"}'
|
|
236
|
+
replacement = f"package-dir = {{{mappings}}}"
|
|
237
|
+
if pyproject.count(original) != 1:
|
|
238
|
+
raise RuntimeError("servo-client editable build could not locate package-dir declaration")
|
|
239
|
+
pyproject_path.write_text(pyproject.replace(original, replacement), encoding="utf-8")
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@contextmanager
|
|
243
|
+
def _staged_project(
|
|
244
|
+
*,
|
|
245
|
+
editable: bool = False,
|
|
246
|
+
include_native: bool = True,
|
|
247
|
+
platform_wheel: bool = False,
|
|
248
|
+
) -> Iterator[None]:
|
|
249
|
+
previous_directory = Path.cwd()
|
|
250
|
+
with tempfile.TemporaryDirectory(prefix="servo-client-build-") as temporary_directory:
|
|
251
|
+
# Resolve macOS' /var -> /private/var symlink before deriving editable
|
|
252
|
+
# package paths. A lexical relative path calculated across that symlink
|
|
253
|
+
# otherwise resolves under /private/Users after setuptools changes cwd.
|
|
254
|
+
stage = Path(temporary_directory).resolve() / "servo-client"
|
|
255
|
+
_copy_project(stage)
|
|
256
|
+
if not include_native:
|
|
257
|
+
_exclude_native_artifact(stage)
|
|
258
|
+
if platform_wheel:
|
|
259
|
+
_configure_platform_wheel(stage)
|
|
260
|
+
if editable:
|
|
261
|
+
_configure_editable_project(stage)
|
|
262
|
+
os.chdir(stage)
|
|
263
|
+
try:
|
|
264
|
+
yield
|
|
265
|
+
finally:
|
|
266
|
+
os.chdir(previous_directory)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _absolute_directory(path: str) -> str:
|
|
270
|
+
return str(Path(path).resolve())
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def get_requires_for_build_wheel(
|
|
274
|
+
config_settings: Mapping[str, Any] | None = None,
|
|
275
|
+
) -> list[str]:
|
|
276
|
+
with _staged_project(platform_wheel=True):
|
|
277
|
+
return _setuptools.get_requires_for_build_wheel(config_settings)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def get_requires_for_build_sdist(
|
|
281
|
+
config_settings: Mapping[str, Any] | None = None,
|
|
282
|
+
) -> list[str]:
|
|
283
|
+
with _staged_project(include_native=False):
|
|
284
|
+
return _setuptools.get_requires_for_build_sdist(config_settings)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def get_requires_for_build_editable(
|
|
288
|
+
config_settings: Mapping[str, Any] | None = None,
|
|
289
|
+
) -> list[str]:
|
|
290
|
+
with _staged_project(editable=True):
|
|
291
|
+
return _setuptools.get_requires_for_build_editable(config_settings)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def prepare_metadata_for_build_wheel(
|
|
295
|
+
metadata_directory: str,
|
|
296
|
+
config_settings: Mapping[str, Any] | None = None,
|
|
297
|
+
) -> str:
|
|
298
|
+
metadata_directory = _absolute_directory(metadata_directory)
|
|
299
|
+
with _staged_project(platform_wheel=True):
|
|
300
|
+
return _setuptools.prepare_metadata_for_build_wheel(
|
|
301
|
+
metadata_directory,
|
|
302
|
+
config_settings,
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def prepare_metadata_for_build_editable(
|
|
307
|
+
metadata_directory: str,
|
|
308
|
+
config_settings: Mapping[str, Any] | None = None,
|
|
309
|
+
) -> str:
|
|
310
|
+
metadata_directory = _absolute_directory(metadata_directory)
|
|
311
|
+
with _staged_project(editable=True):
|
|
312
|
+
return _setuptools.prepare_metadata_for_build_editable(
|
|
313
|
+
metadata_directory,
|
|
314
|
+
config_settings,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def build_wheel(
|
|
319
|
+
wheel_directory: str,
|
|
320
|
+
config_settings: Mapping[str, Any] | None = None,
|
|
321
|
+
metadata_directory: str | None = None,
|
|
322
|
+
) -> str:
|
|
323
|
+
wheel_directory = _absolute_directory(wheel_directory)
|
|
324
|
+
if metadata_directory is not None:
|
|
325
|
+
metadata_directory = _absolute_directory(metadata_directory)
|
|
326
|
+
with _staged_project(platform_wheel=True):
|
|
327
|
+
return _setuptools.build_wheel(
|
|
328
|
+
wheel_directory,
|
|
329
|
+
config_settings,
|
|
330
|
+
metadata_directory,
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def build_editable(
|
|
335
|
+
wheel_directory: str,
|
|
336
|
+
config_settings: Mapping[str, Any] | None = None,
|
|
337
|
+
metadata_directory: str | None = None,
|
|
338
|
+
) -> str:
|
|
339
|
+
wheel_directory = _absolute_directory(wheel_directory)
|
|
340
|
+
if metadata_directory is not None:
|
|
341
|
+
metadata_directory = _absolute_directory(metadata_directory)
|
|
342
|
+
with _staged_project(editable=True):
|
|
343
|
+
return _setuptools.build_editable(
|
|
344
|
+
wheel_directory,
|
|
345
|
+
config_settings,
|
|
346
|
+
metadata_directory,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def build_sdist(
|
|
351
|
+
sdist_directory: str,
|
|
352
|
+
config_settings: Mapping[str, Any] | None = None,
|
|
353
|
+
) -> str:
|
|
354
|
+
sdist_directory = _absolute_directory(sdist_directory)
|
|
355
|
+
with _staged_project(include_native=False):
|
|
356
|
+
return _setuptools.build_sdist(sdist_directory, config_settings)
|