slim-bindings 1.0.0__py3-none-manylinux_2_28_aarch64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- examples/__init__.py +44 -0
- examples/common.py +411 -0
- examples/config.py +298 -0
- examples/example-config.yaml +44 -0
- examples/group.py +399 -0
- examples/point_to_point.py +215 -0
- examples/slim.py +146 -0
- slim_bindings/__init__.py +1 -0
- slim_bindings/libslim_bindings.so +0 -0
- slim_bindings/slim_bindings.py +9780 -0
- slim_bindings-1.0.0.dist-info/METADATA +504 -0
- slim_bindings-1.0.0.dist-info/RECORD +14 -0
- slim_bindings-1.0.0.dist-info/WHEEL +4 -0
- slim_bindings-1.0.0.dist-info/entry_points.txt +4 -0
examples/config.py
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# Copyright AGNTCY Contributors (https://github.com/agntcy)
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""
|
|
4
|
+
Pydantic configuration models for SLIM examples.
|
|
5
|
+
|
|
6
|
+
This module provides a centralized configuration system using Pydantic,
|
|
7
|
+
supporting:
|
|
8
|
+
- Environment variable loading
|
|
9
|
+
- Config file loading (JSON, YAML, TOML)
|
|
10
|
+
- Type validation
|
|
11
|
+
- Sensible defaults
|
|
12
|
+
- Documentation via field descriptions
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
from enum import Enum
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from pydantic import Field, field_validator
|
|
21
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AuthMode(str, Enum):
|
|
25
|
+
"""Authentication mode for SLIM applications."""
|
|
26
|
+
|
|
27
|
+
SHARED_SECRET = "shared_secret"
|
|
28
|
+
JWT = "jwt"
|
|
29
|
+
SPIRE = "spire"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BaseConfig(BaseSettings):
|
|
33
|
+
"""
|
|
34
|
+
Base configuration shared across all SLIM examples.
|
|
35
|
+
|
|
36
|
+
Environment variables are prefixed with SLIM_ by default.
|
|
37
|
+
Example: SLIM_LOCAL=org/ns/app
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
model_config = SettingsConfigDict(
|
|
41
|
+
env_prefix="SLIM_",
|
|
42
|
+
env_file=".env",
|
|
43
|
+
env_file_encoding="utf-8",
|
|
44
|
+
case_sensitive=False,
|
|
45
|
+
extra="ignore",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Core identity settings
|
|
49
|
+
local: str = Field(
|
|
50
|
+
...,
|
|
51
|
+
description="Local ID in the format organization/namespace/application",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
remote: str | None = Field(
|
|
55
|
+
None,
|
|
56
|
+
description="Remote ID in the format organization/namespace/application-or-stream",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Service connection
|
|
60
|
+
slim: str = Field(
|
|
61
|
+
default="http://127.0.0.1:46357",
|
|
62
|
+
description="SLIM remote endpoint URL",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Feature flags
|
|
66
|
+
enable_opentelemetry: bool = Field(
|
|
67
|
+
default=False,
|
|
68
|
+
description="Enable OpenTelemetry tracing",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
enable_mls: bool = Field(
|
|
72
|
+
default=False,
|
|
73
|
+
description="Enable MLS (Message Layer Security) for sessions",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Shared secret authentication (default, not for production)
|
|
77
|
+
shared_secret: str = Field(
|
|
78
|
+
default="abcde-12345-fedcb-67890-deadc",
|
|
79
|
+
description="Shared secret for authentication (development only)",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# JWT authentication
|
|
83
|
+
jwt: str | None = Field(
|
|
84
|
+
None,
|
|
85
|
+
description="Path to static JWT token file for authentication",
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
spire_trust_bundle: str | None = Field(
|
|
89
|
+
None,
|
|
90
|
+
description="Path to SPIRE trust bundle file (for JWT + JWKS mode)",
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
audience: list[str] | None = Field(
|
|
94
|
+
None,
|
|
95
|
+
description="Audience list for JWT verification",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# SPIRE dynamic identity
|
|
99
|
+
spire_socket_path: str | None = Field(
|
|
100
|
+
None,
|
|
101
|
+
description="SPIRE Workload API socket path",
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
spire_target_spiffe_id: str | None = Field(
|
|
105
|
+
None,
|
|
106
|
+
description="Target SPIFFE ID to request from SPIRE",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
spire_jwt_audience: list[str] | None = Field(
|
|
110
|
+
default=None,
|
|
111
|
+
description="Audience(s) for SPIRE JWT SVID requests",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
@field_validator("audience", mode="before")
|
|
115
|
+
@classmethod
|
|
116
|
+
def parse_audience(cls, v: Any) -> list[str] | None:
|
|
117
|
+
"""Parse audience from comma-separated string or list."""
|
|
118
|
+
if v is None:
|
|
119
|
+
return None
|
|
120
|
+
if isinstance(v, str):
|
|
121
|
+
return [a.strip() for a in v.split(",") if a.strip()]
|
|
122
|
+
return v
|
|
123
|
+
|
|
124
|
+
@field_validator("jwt", "spire_trust_bundle", mode="after")
|
|
125
|
+
@classmethod
|
|
126
|
+
def validate_file_paths(cls, v: str | None) -> str | None:
|
|
127
|
+
"""Validate that file paths exist if provided."""
|
|
128
|
+
if v is not None and not Path(v).exists():
|
|
129
|
+
raise ValueError(f"File not found: {v}")
|
|
130
|
+
return v
|
|
131
|
+
|
|
132
|
+
def get_auth_mode(self) -> AuthMode:
|
|
133
|
+
"""Determine which authentication mode to use based on provided config."""
|
|
134
|
+
if (
|
|
135
|
+
self.spire_socket_path
|
|
136
|
+
or self.spire_target_spiffe_id
|
|
137
|
+
or self.spire_jwt_audience
|
|
138
|
+
):
|
|
139
|
+
return AuthMode.SPIRE
|
|
140
|
+
elif self.jwt and self.spire_trust_bundle and self.audience:
|
|
141
|
+
return AuthMode.JWT
|
|
142
|
+
else:
|
|
143
|
+
return AuthMode.SHARED_SECRET
|
|
144
|
+
|
|
145
|
+
@classmethod
|
|
146
|
+
def from_args(cls, **kwargs: Any) -> "BaseConfig":
|
|
147
|
+
"""
|
|
148
|
+
Create config from keyword arguments.
|
|
149
|
+
|
|
150
|
+
This allows programmatic creation while still validating fields.
|
|
151
|
+
"""
|
|
152
|
+
# Filter out None values to let defaults take effect
|
|
153
|
+
filtered = {k: v for k, v in kwargs.items() if v is not None}
|
|
154
|
+
return cls(**filtered)
|
|
155
|
+
|
|
156
|
+
@classmethod
|
|
157
|
+
def from_file(cls, config_path: str) -> "BaseConfig":
|
|
158
|
+
"""
|
|
159
|
+
Load configuration from a file (JSON, YAML, or TOML).
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
config_path: Path to configuration file
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
Config instance with loaded values
|
|
166
|
+
"""
|
|
167
|
+
path = Path(config_path)
|
|
168
|
+
if not path.exists():
|
|
169
|
+
raise FileNotFoundError(f"Config file not found: {config_path}")
|
|
170
|
+
|
|
171
|
+
content = path.read_text()
|
|
172
|
+
suffix = path.suffix.lower()
|
|
173
|
+
|
|
174
|
+
if suffix == ".json":
|
|
175
|
+
import json
|
|
176
|
+
|
|
177
|
+
data = json.loads(content)
|
|
178
|
+
elif suffix in (".yaml", ".yml"):
|
|
179
|
+
try:
|
|
180
|
+
import yaml # type: ignore[import-untyped]
|
|
181
|
+
|
|
182
|
+
data = yaml.safe_load(content)
|
|
183
|
+
except ImportError:
|
|
184
|
+
raise ImportError(
|
|
185
|
+
"PyYAML is required for YAML config files. Install with: pip install pyyaml"
|
|
186
|
+
)
|
|
187
|
+
elif suffix == ".toml":
|
|
188
|
+
try:
|
|
189
|
+
import tomli # type: ignore[import-untyped]
|
|
190
|
+
|
|
191
|
+
data = tomli.loads(content)
|
|
192
|
+
except ImportError:
|
|
193
|
+
raise ImportError(
|
|
194
|
+
"tomli is required for TOML config files. Install with: pip install tomli"
|
|
195
|
+
)
|
|
196
|
+
else:
|
|
197
|
+
raise ValueError(f"Unsupported config file format: {suffix}")
|
|
198
|
+
|
|
199
|
+
return cls(**data)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class GroupConfig(BaseConfig):
|
|
203
|
+
"""Configuration specific to group messaging examples."""
|
|
204
|
+
|
|
205
|
+
invites: list[str] | None = Field(
|
|
206
|
+
default=None,
|
|
207
|
+
description="List of participant IDs to invite to the group session",
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
@field_validator("invites", mode="before")
|
|
211
|
+
@classmethod
|
|
212
|
+
def parse_invites(cls, v: Any) -> list[str] | None:
|
|
213
|
+
"""Parse invites from comma-separated string or list."""
|
|
214
|
+
if v is None:
|
|
215
|
+
return None
|
|
216
|
+
if isinstance(v, str):
|
|
217
|
+
return [i.strip() for i in v.split(",") if i.strip()]
|
|
218
|
+
return v
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class PointToPointConfig(BaseConfig):
|
|
222
|
+
"""Configuration specific to point-to-point messaging examples."""
|
|
223
|
+
|
|
224
|
+
message: str | None = Field(
|
|
225
|
+
None,
|
|
226
|
+
description="Message to send (activates sender mode)",
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
iterations: int = Field(
|
|
230
|
+
default=10,
|
|
231
|
+
description="Number of request/reply cycles in sender mode",
|
|
232
|
+
ge=1,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class ServerConfig(BaseSettings):
|
|
237
|
+
"""Configuration for SLIM server."""
|
|
238
|
+
|
|
239
|
+
model_config = SettingsConfigDict(
|
|
240
|
+
env_prefix="SLIM_",
|
|
241
|
+
env_file=".env",
|
|
242
|
+
env_file_encoding="utf-8",
|
|
243
|
+
case_sensitive=False,
|
|
244
|
+
extra="ignore",
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
slim: str = Field(
|
|
248
|
+
default="127.0.0.1:12345",
|
|
249
|
+
description="SLIM server address (host:port)",
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
enable_opentelemetry: bool = Field(
|
|
253
|
+
default=False,
|
|
254
|
+
description="Enable OpenTelemetry tracing",
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
@classmethod
|
|
258
|
+
def from_args(cls, **kwargs: Any) -> "ServerConfig":
|
|
259
|
+
"""Create config from keyword arguments."""
|
|
260
|
+
filtered = {k: v for k, v in kwargs.items() if v is not None}
|
|
261
|
+
return cls(**filtered)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def load_config_with_cli_override(
|
|
265
|
+
config_class: type[BaseSettings], cli_args: dict[str, Any]
|
|
266
|
+
) -> BaseSettings:
|
|
267
|
+
"""
|
|
268
|
+
Load configuration with CLI argument override priority.
|
|
269
|
+
|
|
270
|
+
Priority order (highest to lowest):
|
|
271
|
+
1. CLI arguments (non-None values)
|
|
272
|
+
2. Environment variables
|
|
273
|
+
3. Config file (if SLIM_CONFIG_FILE env var is set)
|
|
274
|
+
4. Defaults
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
config_class: The Pydantic settings class to instantiate
|
|
278
|
+
cli_args: Dictionary of CLI arguments
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
Configured settings instance
|
|
282
|
+
"""
|
|
283
|
+
# Check if config file is specified
|
|
284
|
+
config_file = os.getenv("SLIM_CONFIG_FILE")
|
|
285
|
+
|
|
286
|
+
if config_file and hasattr(config_class, "from_file"):
|
|
287
|
+
# Load from file first
|
|
288
|
+
config = config_class.from_file(config_file)
|
|
289
|
+
# Override with CLI args
|
|
290
|
+
if cli_args:
|
|
291
|
+
filtered_cli = {k: v for k, v in cli_args.items() if v is not None}
|
|
292
|
+
config = config_class(**{**config.model_dump(), **filtered_cli})
|
|
293
|
+
else:
|
|
294
|
+
# Load from env vars and CLI args
|
|
295
|
+
filtered_cli = {k: v for k, v in cli_args.items() if v is not None}
|
|
296
|
+
config = config_class(**filtered_cli)
|
|
297
|
+
|
|
298
|
+
return config
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Copyright AGNTCY Contributors (https://github.com/agntcy)
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
# Example YAML configuration for SLIM examples
|
|
5
|
+
# Usage: SLIM_CONFIG_FILE=example-config.yaml slim-bindings-group
|
|
6
|
+
|
|
7
|
+
# Core identity settings (required)
|
|
8
|
+
local: "org/namespace/my-app"
|
|
9
|
+
remote: "org/namespace/remote-app" # Optional for some examples
|
|
10
|
+
|
|
11
|
+
# Service connection
|
|
12
|
+
slim: "http://127.0.0.1:46357"
|
|
13
|
+
|
|
14
|
+
# Feature flags
|
|
15
|
+
enable_opentelemetry: false
|
|
16
|
+
enable_mls: false
|
|
17
|
+
|
|
18
|
+
# Authentication options (choose one)
|
|
19
|
+
# Option 1: Shared secret (default, development only)
|
|
20
|
+
shared_secret: "abcde-12345-fedcb-67890-deadc"
|
|
21
|
+
|
|
22
|
+
# Option 2: JWT with JWKS (uncomment to use)
|
|
23
|
+
# jwt: "/path/to/jwt-token.pem"
|
|
24
|
+
# spire_trust_bundle: "/path/to/spire-bundle.json"
|
|
25
|
+
# audience:
|
|
26
|
+
# - "audience1"
|
|
27
|
+
# - "audience2"
|
|
28
|
+
|
|
29
|
+
# Option 3: SPIRE dynamic identity (uncomment to use)
|
|
30
|
+
# spire_socket_path: "/run/spire/sockets/agent.sock"
|
|
31
|
+
# spire_target_spiffe_id: "spiffe://example.org/my-service"
|
|
32
|
+
# spire_jwt_audience:
|
|
33
|
+
# - "slim-service"
|
|
34
|
+
# - "other-service"
|
|
35
|
+
|
|
36
|
+
# Group-specific options (for group.py)
|
|
37
|
+
invites:
|
|
38
|
+
- "org/namespace/peer1"
|
|
39
|
+
- "org/namespace/peer2"
|
|
40
|
+
- "org/namespace/peer3"
|
|
41
|
+
|
|
42
|
+
# Point-to-point specific options (for point_to_point.py)
|
|
43
|
+
message: "Hello from SLIM!"
|
|
44
|
+
iterations: 10
|