arclith-cli 0.13.0__tar.gz → 0.14.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.
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/PKG-INFO +2 -2
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/README.md +5 -3
- arclith_cli-0.14.0/arclith_cli/__init__.py +1 -0
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/arclith_cli/add_adapter.py +8 -0
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/arclith_cli/capabilities.py +233 -0
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/pyproject.toml +2 -2
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/tests/e2e_manual.sh +1 -1
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/tests/test_add_adapter.py +142 -1
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/tests/test_capabilities.py +108 -1
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/tests/test_core_scaffold.py +1 -1
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/uv.lock +11 -3
- arclith_cli-0.13.0/arclith_cli/__init__.py +0 -1
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/.gitignore +0 -0
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/LICENSE +0 -0
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/Makefile +0 -0
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/arclith_cli/adapter_templates.py +0 -0
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/arclith_cli/core_scaffold.py +0 -0
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/arclith_cli/entity_scanner.py +0 -0
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/arclith_cli/export_config.py +0 -0
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/arclith_cli/init_project.py +0 -0
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/arclith_cli/main.py +0 -0
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/arclith_cli/project_paths.py +0 -0
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/arclith_cli/rename.py +0 -0
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/arclith_cli/runtime_templates.py +0 -0
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/arclith_cli/scaffold.py +0 -0
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/arclith_cli/updater.py +0 -0
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/tests/__init__.py +0 -0
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/tests/test_e2e_scaffold.py +0 -0
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/tests/test_project_paths.py +0 -0
- {arclith_cli-0.13.0 → arclith_cli-0.14.0}/tests/test_rename.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: arclith-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.14.0
|
|
4
4
|
Summary: CLI scaffolding tool for arclith — hexagonal architecture framework
|
|
5
5
|
Author-email: Killian KOPP <killiankopp@gmail.com>
|
|
6
6
|
License: Apache License
|
|
@@ -186,7 +186,7 @@ License: Apache License
|
|
|
186
186
|
License-File: LICENSE
|
|
187
187
|
Keywords: arclith,cli,ddd,hexagonal-architecture,scaffold
|
|
188
188
|
Requires-Python: >=3.13
|
|
189
|
-
Requires-Dist: arclith>=0.
|
|
189
|
+
Requires-Dist: arclith>=0.17.0
|
|
190
190
|
Requires-Dist: httpx>=0.27.0
|
|
191
191
|
Requires-Dist: rich>=13.0.0
|
|
192
192
|
Requires-Dist: typer>=0.15.0
|
|
@@ -141,7 +141,7 @@ arclith-cli add-adapter --adapter mariadb --entity Recipe --param database=my_re
|
|
|
141
141
|
arclith-cli add-adapter --capability api --adapter fastapi --param port=8080 --yes
|
|
142
142
|
arclith-cli add-adapter --capability mcp --adapter fastmcp --param port=8081 --yes
|
|
143
143
|
arclith-cli add-adapter --capability llm --adapter lmstudio --param model_name=qwen/qwen3.5-9b --yes
|
|
144
|
-
arclith-cli add-adapter --capability agent --adapter langgraph --param graph_name=recipe_agent --yes
|
|
144
|
+
arclith-cli add-adapter --capability agent --adapter langgraph --param graph_name=recipe_agent --param stream_mode=updates,custom --yes
|
|
145
145
|
arclith-cli add-adapter --capability observability --adapter langsmith
|
|
146
146
|
arclith-cli add-adapter --capability observability --adapter opentelemetry --param service_name=my_recipe_service --yes
|
|
147
147
|
arclith-cli add-adapter --capability runtime --adapter docker-image --yes
|
|
@@ -166,7 +166,7 @@ arclith-cli add-adapter --capability repository --adapter memory --entity Recipe
|
|
|
166
166
|
- `lmstudio` → `model_name`, `base_url`, `api_key`
|
|
167
167
|
- `openai` → `model_name`, `base_url`, `OPENAI_API_KEY`
|
|
168
168
|
- `anthropic` → `model_name`, `ANTHROPIC_API_KEY`
|
|
169
|
-
- `langgraph` → `graph_name`
|
|
169
|
+
- `langgraph` → `graph_name`, `stream_mode`
|
|
170
170
|
- `langsmith` → `tracing`, `project`, `endpoint`, `LANGSMITH_API_KEY`
|
|
171
171
|
- `opentelemetry` → `service_name`, `endpoint`, `traces_endpoint`, `metrics_endpoint`, `protocol`, `traces`, `metrics`, `instrument_fastapi`
|
|
172
172
|
- `command-bus/rabbitmq` → `url`, `exchange`, `exchange_type`, `queue`, `routing_key`, `prefetch`, `consumer_name`, `concurrency`, `publisher_confirms`, `durable`, `retry_enabled`, `retry_requeue`, `dead_letter_exchange`, `dead_letter_routing_key`
|
|
@@ -240,7 +240,9 @@ l'environnement, un fichier local de secrets ou Vault selon le resolver choisi.
|
|
|
240
240
|
L'adapter `agent/langgraph` génère `langgraph.json`, `config/adapters/inbound/langgraph.yaml` et
|
|
241
241
|
`src/<package>/adapters/inbound/langgraph/agent.py`. Le projet ne modifie ensuite que ce fichier pour
|
|
242
242
|
son agent. Comme `fastapi` et `fastmcp`, LangGraph est configuré par son nom produit dans
|
|
243
|
-
`AppConfig.langgraph`, sans `adapters.agent`.
|
|
243
|
+
`AppConfig.langgraph`, sans `adapters.agent`. `stream_mode` vaut `updates` par défaut et accepte une
|
|
244
|
+
liste CSV comme `updates,custom` pour exposer les sorties de nodes et les événements
|
|
245
|
+
`get_stream_writer()`. L'adapter `observability/langsmith` génère
|
|
244
246
|
`config/adapters/outbound/langsmith.yaml`, l'ajoute à `observability.enabled`, met
|
|
245
247
|
à jour `.env` et ajoute `.env` au `.gitignore` si besoin. LangSmith Studio devient l'endroit standard
|
|
246
248
|
pour tester les agents. Une `LANGSMITH_API_KEY` déjà présente est conservée si aucune nouvelle valeur
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.14.0"
|
|
@@ -686,11 +686,19 @@ def _file_template_vars(
|
|
|
686
686
|
"package_path": package_path,
|
|
687
687
|
"langgraph_entrypoint": langgraph_entrypoint,
|
|
688
688
|
"graph_name": graph_name,
|
|
689
|
+
"stream_mode_yaml": _langgraph_stream_mode_yaml(str(params.get("stream_mode") or "updates")),
|
|
689
690
|
"secret_template_yaml": _secret_template_yaml(str(params.get("field_path") or "")),
|
|
690
691
|
"secret_chain_yaml": _secret_chain_yaml(str(params.get("resolvers") or "")),
|
|
691
692
|
}
|
|
692
693
|
|
|
693
694
|
|
|
695
|
+
def _langgraph_stream_mode_yaml(stream_mode: str) -> str:
|
|
696
|
+
values = _split_csv_values(stream_mode)
|
|
697
|
+
if len(values) == 1:
|
|
698
|
+
return f'"{values[0]}"'
|
|
699
|
+
return yaml.safe_dump(values, default_flow_style=True, sort_keys=False).strip()
|
|
700
|
+
|
|
701
|
+
|
|
694
702
|
def _secret_template_yaml(field_path: str) -> str:
|
|
695
703
|
keys = [key for key in field_path.split(".") if key]
|
|
696
704
|
if not keys:
|
|
@@ -287,6 +287,226 @@ multitenant: false
|
|
|
287
287
|
),
|
|
288
288
|
)
|
|
289
289
|
|
|
290
|
+
STORAGE_CAPABILITY = CapabilitySpec(
|
|
291
|
+
name="storage",
|
|
292
|
+
layer="outbound",
|
|
293
|
+
description="Stockage de fichiers et blobs derrière un port FileStoragePort.",
|
|
294
|
+
activation_config_key=None,
|
|
295
|
+
adapters=(
|
|
296
|
+
AdapterSpec(
|
|
297
|
+
name="filesystem",
|
|
298
|
+
capability="storage",
|
|
299
|
+
layer="outbound",
|
|
300
|
+
description="Stockage fichier local, typiquement monté dans Docker via un volume.",
|
|
301
|
+
config_path="config/adapters/outbound/storage.yaml",
|
|
302
|
+
config_template="""\
|
|
303
|
+
adapter: filesystem
|
|
304
|
+
root_path: "{root_path}"
|
|
305
|
+
prefix: "{prefix}"
|
|
306
|
+
create_root: {create_root}
|
|
307
|
+
multitenant: {multitenant}
|
|
308
|
+
""",
|
|
309
|
+
parameters=(
|
|
310
|
+
ParameterSpec(
|
|
311
|
+
name="root_path",
|
|
312
|
+
kind="string",
|
|
313
|
+
prompt="Chemin conteneur du dossier de fichiers",
|
|
314
|
+
default="/data/files",
|
|
315
|
+
),
|
|
316
|
+
ParameterSpec(
|
|
317
|
+
name="prefix",
|
|
318
|
+
kind="string",
|
|
319
|
+
prompt="Préfixe d'objets",
|
|
320
|
+
default="",
|
|
321
|
+
),
|
|
322
|
+
ParameterSpec(
|
|
323
|
+
name="create_root",
|
|
324
|
+
kind="boolean",
|
|
325
|
+
prompt="Créer le dossier racine au démarrage",
|
|
326
|
+
default=True,
|
|
327
|
+
),
|
|
328
|
+
ParameterSpec(
|
|
329
|
+
name="multitenant",
|
|
330
|
+
kind="boolean",
|
|
331
|
+
prompt="Résoudre le stockage par tenant",
|
|
332
|
+
default=False,
|
|
333
|
+
),
|
|
334
|
+
),
|
|
335
|
+
entity_scoped=False,
|
|
336
|
+
),
|
|
337
|
+
AdapterSpec(
|
|
338
|
+
name="s3",
|
|
339
|
+
capability="storage",
|
|
340
|
+
layer="outbound",
|
|
341
|
+
description="Stockage objet AWS S3 compatible MinIO via endpoint custom.",
|
|
342
|
+
config_path="config/adapters/outbound/storage.yaml",
|
|
343
|
+
config_template="""\
|
|
344
|
+
adapter: s3
|
|
345
|
+
# Credentials: chaîne AWS par défaut, variables d'environnement, rôle IAM ou TenantContext.
|
|
346
|
+
bucket_name: "{bucket_name}"
|
|
347
|
+
prefix: "{prefix}"
|
|
348
|
+
region_name: "{region_name}"
|
|
349
|
+
endpoint_url: {endpoint_url}
|
|
350
|
+
force_path_style: {force_path_style}
|
|
351
|
+
multitenant: {multitenant}
|
|
352
|
+
""",
|
|
353
|
+
parameters=(
|
|
354
|
+
ParameterSpec(
|
|
355
|
+
name="bucket_name",
|
|
356
|
+
kind="string",
|
|
357
|
+
prompt="Bucket S3",
|
|
358
|
+
default="my-bucket",
|
|
359
|
+
),
|
|
360
|
+
ParameterSpec(
|
|
361
|
+
name="prefix",
|
|
362
|
+
kind="string",
|
|
363
|
+
prompt="Préfixe d'objets",
|
|
364
|
+
default="",
|
|
365
|
+
),
|
|
366
|
+
ParameterSpec(
|
|
367
|
+
name="region_name",
|
|
368
|
+
kind="string",
|
|
369
|
+
prompt="Région AWS",
|
|
370
|
+
default="eu-west-3",
|
|
371
|
+
),
|
|
372
|
+
ParameterSpec(
|
|
373
|
+
name="endpoint_url",
|
|
374
|
+
kind="string",
|
|
375
|
+
prompt="Endpoint S3 custom ou null",
|
|
376
|
+
default="null",
|
|
377
|
+
),
|
|
378
|
+
ParameterSpec(
|
|
379
|
+
name="force_path_style",
|
|
380
|
+
kind="boolean",
|
|
381
|
+
prompt="Forcer le path-style S3",
|
|
382
|
+
default=False,
|
|
383
|
+
),
|
|
384
|
+
ParameterSpec(
|
|
385
|
+
name="multitenant",
|
|
386
|
+
kind="boolean",
|
|
387
|
+
prompt="Résoudre le stockage par tenant",
|
|
388
|
+
default=False,
|
|
389
|
+
),
|
|
390
|
+
),
|
|
391
|
+
entity_scoped=False,
|
|
392
|
+
),
|
|
393
|
+
AdapterSpec(
|
|
394
|
+
name="azure-blob",
|
|
395
|
+
capability="storage",
|
|
396
|
+
layer="outbound",
|
|
397
|
+
description="Stockage objet Azure Blob Storage.",
|
|
398
|
+
config_path="config/adapters/outbound/storage.yaml",
|
|
399
|
+
config_template="""\
|
|
400
|
+
adapter: azure-blob
|
|
401
|
+
account_url: "{account_url}"
|
|
402
|
+
container_name: "{container_name}"
|
|
403
|
+
prefix: "{prefix}"
|
|
404
|
+
connection_string: null
|
|
405
|
+
account_key: null
|
|
406
|
+
sas_token: null
|
|
407
|
+
use_default_credential: {use_default_credential}
|
|
408
|
+
multitenant: {multitenant}
|
|
409
|
+
# Credentials: mappez connection_string, account_key ou sas_token via
|
|
410
|
+
# config/secrets.yaml, Vault, ou TenantContext. use_default_credential=true
|
|
411
|
+
# délègue à DefaultAzureCredential du SDK Azure.
|
|
412
|
+
""",
|
|
413
|
+
secret_mappings=(
|
|
414
|
+
SecretMappingSpec(
|
|
415
|
+
field_path="adapters.storage.connection_string",
|
|
416
|
+
secret_key="AZURE_STORAGE_CONNECTION_STRING",
|
|
417
|
+
),
|
|
418
|
+
SecretMappingSpec(
|
|
419
|
+
field_path="adapters.storage.account_key",
|
|
420
|
+
secret_key="AZURE_STORAGE_ACCOUNT_KEY",
|
|
421
|
+
),
|
|
422
|
+
SecretMappingSpec(
|
|
423
|
+
field_path="adapters.storage.sas_token",
|
|
424
|
+
secret_key="AZURE_STORAGE_SAS_TOKEN",
|
|
425
|
+
),
|
|
426
|
+
),
|
|
427
|
+
parameters=(
|
|
428
|
+
ParameterSpec(
|
|
429
|
+
name="account_url",
|
|
430
|
+
kind="string",
|
|
431
|
+
prompt="URL du compte Azure Blob",
|
|
432
|
+
default="https://<account>.blob.core.windows.net",
|
|
433
|
+
),
|
|
434
|
+
ParameterSpec(
|
|
435
|
+
name="container_name",
|
|
436
|
+
kind="string",
|
|
437
|
+
prompt="Container Azure Blob",
|
|
438
|
+
default="my-container",
|
|
439
|
+
),
|
|
440
|
+
ParameterSpec(
|
|
441
|
+
name="prefix",
|
|
442
|
+
kind="string",
|
|
443
|
+
prompt="Préfixe d'objets",
|
|
444
|
+
default="",
|
|
445
|
+
),
|
|
446
|
+
ParameterSpec(
|
|
447
|
+
name="use_default_credential",
|
|
448
|
+
kind="boolean",
|
|
449
|
+
prompt="Utiliser DefaultAzureCredential",
|
|
450
|
+
default=False,
|
|
451
|
+
),
|
|
452
|
+
ParameterSpec(
|
|
453
|
+
name="multitenant",
|
|
454
|
+
kind="boolean",
|
|
455
|
+
prompt="Résoudre le stockage par tenant",
|
|
456
|
+
default=False,
|
|
457
|
+
),
|
|
458
|
+
),
|
|
459
|
+
entity_scoped=False,
|
|
460
|
+
),
|
|
461
|
+
AdapterSpec(
|
|
462
|
+
name="gcs",
|
|
463
|
+
capability="storage",
|
|
464
|
+
layer="outbound",
|
|
465
|
+
description="Stockage objet Google Cloud Storage.",
|
|
466
|
+
config_path="config/adapters/outbound/storage.yaml",
|
|
467
|
+
config_template="""\
|
|
468
|
+
adapter: gcs
|
|
469
|
+
bucket_name: "{bucket_name}"
|
|
470
|
+
prefix: "{prefix}"
|
|
471
|
+
project_id: {project_id}
|
|
472
|
+
credentials_path: null
|
|
473
|
+
credentials_json: null
|
|
474
|
+
credentials_json_b64: null
|
|
475
|
+
multitenant: {multitenant}
|
|
476
|
+
# Credentials: use ADC/GOOGLE_APPLICATION_CREDENTIALS, config/secrets.yaml,
|
|
477
|
+
# Vault, or TenantContext. Never commit service account JSON.
|
|
478
|
+
""",
|
|
479
|
+
parameters=(
|
|
480
|
+
ParameterSpec(
|
|
481
|
+
name="bucket_name",
|
|
482
|
+
kind="string",
|
|
483
|
+
prompt="Bucket Google Cloud Storage",
|
|
484
|
+
default="my-bucket",
|
|
485
|
+
),
|
|
486
|
+
ParameterSpec(
|
|
487
|
+
name="prefix",
|
|
488
|
+
kind="string",
|
|
489
|
+
prompt="Préfixe d'objets",
|
|
490
|
+
default="",
|
|
491
|
+
),
|
|
492
|
+
ParameterSpec(
|
|
493
|
+
name="project_id",
|
|
494
|
+
kind="string",
|
|
495
|
+
prompt="Project ID GCP ou null",
|
|
496
|
+
default="null",
|
|
497
|
+
),
|
|
498
|
+
ParameterSpec(
|
|
499
|
+
name="multitenant",
|
|
500
|
+
kind="boolean",
|
|
501
|
+
prompt="Résoudre le stockage par tenant",
|
|
502
|
+
default=False,
|
|
503
|
+
),
|
|
504
|
+
),
|
|
505
|
+
entity_scoped=False,
|
|
506
|
+
),
|
|
507
|
+
),
|
|
508
|
+
)
|
|
509
|
+
|
|
290
510
|
CACHE_CAPABILITY = CapabilitySpec(
|
|
291
511
|
name="cache",
|
|
292
512
|
layer="outbound",
|
|
@@ -1263,6 +1483,7 @@ name: "{graph_name}"
|
|
|
1263
1483
|
graph: "{graph_name}"
|
|
1264
1484
|
entrypoint: "{langgraph_entrypoint}"
|
|
1265
1485
|
env: ".env"
|
|
1486
|
+
stream_mode: {stream_mode_yaml}
|
|
1266
1487
|
""",
|
|
1267
1488
|
file_templates=(
|
|
1268
1489
|
FileTemplateSpec(
|
|
@@ -1287,6 +1508,7 @@ env: ".env"
|
|
|
1287
1508
|
from typing import Any, TypedDict
|
|
1288
1509
|
|
|
1289
1510
|
from arclith import Arclith
|
|
1511
|
+
from langgraph.config import get_stream_writer
|
|
1290
1512
|
from langgraph.graph import END, START
|
|
1291
1513
|
|
|
1292
1514
|
|
|
@@ -1300,6 +1522,8 @@ arclith = Arclith("config")
|
|
|
1300
1522
|
# Template minimal volontaire: remplacer AgentState, run_agent et les edges par
|
|
1301
1523
|
# l'état, les noeuds et les transitions propres au projet.
|
|
1302
1524
|
async def run_agent(state: AgentState) -> AgentState:
|
|
1525
|
+
writer = get_stream_writer()
|
|
1526
|
+
writer({{"kind": "progress", "stage": "agent.started", "message": "Agent node started."}})
|
|
1303
1527
|
return state
|
|
1304
1528
|
|
|
1305
1529
|
|
|
@@ -1320,6 +1544,14 @@ agent = arclith.langgraph(AgentState, register_agent, name="{graph_name}")
|
|
|
1320
1544
|
prompt="Nom du graphe LangGraph",
|
|
1321
1545
|
default="agent",
|
|
1322
1546
|
),
|
|
1547
|
+
ParameterSpec(
|
|
1548
|
+
name="stream_mode",
|
|
1549
|
+
kind="string",
|
|
1550
|
+
prompt="Mode(s) de streaming LangGraph",
|
|
1551
|
+
default="updates",
|
|
1552
|
+
choices=("values", "updates", "custom", "messages", "checkpoints", "tasks", "debug"),
|
|
1553
|
+
csv_choices=True,
|
|
1554
|
+
),
|
|
1323
1555
|
),
|
|
1324
1556
|
entity_scoped=False,
|
|
1325
1557
|
),
|
|
@@ -1478,6 +1710,7 @@ OTEL_EXPORTER_OTLP_HEADERS={headers}
|
|
|
1478
1710
|
|
|
1479
1711
|
CAPABILITY_CATALOG = (
|
|
1480
1712
|
REPOSITORY_CAPABILITY,
|
|
1713
|
+
STORAGE_CAPABILITY,
|
|
1481
1714
|
CACHE_CAPABILITY,
|
|
1482
1715
|
LOGGER_CAPABILITY,
|
|
1483
1716
|
SECRETS_CAPABILITY,
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "arclith-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.14.0"
|
|
8
8
|
description = "CLI scaffolding tool for arclith — hexagonal architecture framework"
|
|
9
9
|
license = { file = "LICENSE" }
|
|
10
10
|
requires-python = ">=3.13"
|
|
@@ -16,7 +16,7 @@ dependencies = [
|
|
|
16
16
|
"typer>=0.15.0",
|
|
17
17
|
"rich>=13.0.0",
|
|
18
18
|
"httpx>=0.27.0",
|
|
19
|
-
"arclith>=0.
|
|
19
|
+
"arclith>=0.17.0",
|
|
20
20
|
]
|
|
21
21
|
|
|
22
22
|
[tool.uv.sources]
|
|
@@ -39,7 +39,7 @@ if grep -q '\[tool\.uv\.sources\]' pyproject.toml; then
|
|
|
39
39
|
fi
|
|
40
40
|
|
|
41
41
|
ARCLITH_VERSION=$(grep 'arclith\[' pyproject.toml | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
|
|
42
|
-
MIN_ARCLITH_VERSION="0.
|
|
42
|
+
MIN_ARCLITH_VERSION="0.17.0"
|
|
43
43
|
if version_lt "$ARCLITH_VERSION" "$MIN_ARCLITH_VERSION"; then
|
|
44
44
|
echo -e "${RED}✗ FAIL: arclith version < $MIN_ARCLITH_VERSION (found: $ARCLITH_VERSION)${NC}"
|
|
45
45
|
exit 1
|
|
@@ -868,6 +868,141 @@ def test_add_lmstudio_llm_adapter_generates_lm_config_only(tmp_path: Path) -> No
|
|
|
868
868
|
assert arclith.config.adapters.lm.base_url == "http://127.0.0.1:1234/v1"
|
|
869
869
|
|
|
870
870
|
|
|
871
|
+
@pytest.mark.parametrize(
|
|
872
|
+
("adapter", "params", "expected"),
|
|
873
|
+
[
|
|
874
|
+
(
|
|
875
|
+
"filesystem",
|
|
876
|
+
{
|
|
877
|
+
"root_path": "/data/files",
|
|
878
|
+
"prefix": "uploads",
|
|
879
|
+
"create_root": "true",
|
|
880
|
+
"multitenant": "false",
|
|
881
|
+
},
|
|
882
|
+
{
|
|
883
|
+
"adapter": "filesystem",
|
|
884
|
+
"root_path": "/data/files",
|
|
885
|
+
"prefix": "uploads",
|
|
886
|
+
"create_root": True,
|
|
887
|
+
"multitenant": False,
|
|
888
|
+
},
|
|
889
|
+
),
|
|
890
|
+
(
|
|
891
|
+
"s3",
|
|
892
|
+
{
|
|
893
|
+
"bucket_name": "arclith-files",
|
|
894
|
+
"prefix": "uploads",
|
|
895
|
+
"region_name": "eu-west-3",
|
|
896
|
+
"endpoint_url": "null",
|
|
897
|
+
"force_path_style": "false",
|
|
898
|
+
"multitenant": "false",
|
|
899
|
+
},
|
|
900
|
+
{
|
|
901
|
+
"adapter": "s3",
|
|
902
|
+
"bucket_name": "arclith-files",
|
|
903
|
+
"prefix": "uploads",
|
|
904
|
+
"region_name": "eu-west-3",
|
|
905
|
+
"endpoint_url": None,
|
|
906
|
+
"force_path_style": False,
|
|
907
|
+
"multitenant": False,
|
|
908
|
+
},
|
|
909
|
+
),
|
|
910
|
+
(
|
|
911
|
+
"azure-blob",
|
|
912
|
+
{
|
|
913
|
+
"account_url": "https://account.blob.core.windows.net",
|
|
914
|
+
"container_name": "arclith-files",
|
|
915
|
+
"prefix": "uploads",
|
|
916
|
+
"use_default_credential": "false",
|
|
917
|
+
"multitenant": "false",
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
"adapter": "azure-blob",
|
|
921
|
+
"account_url": "https://account.blob.core.windows.net",
|
|
922
|
+
"container_name": "arclith-files",
|
|
923
|
+
"prefix": "uploads",
|
|
924
|
+
"connection_string": None,
|
|
925
|
+
"account_key": None,
|
|
926
|
+
"sas_token": None,
|
|
927
|
+
"use_default_credential": False,
|
|
928
|
+
"multitenant": False,
|
|
929
|
+
},
|
|
930
|
+
),
|
|
931
|
+
(
|
|
932
|
+
"gcs",
|
|
933
|
+
{
|
|
934
|
+
"bucket_name": "arclith-files",
|
|
935
|
+
"prefix": "uploads",
|
|
936
|
+
"project_id": "null",
|
|
937
|
+
"multitenant": "false",
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
"adapter": "gcs",
|
|
941
|
+
"bucket_name": "arclith-files",
|
|
942
|
+
"prefix": "uploads",
|
|
943
|
+
"project_id": None,
|
|
944
|
+
"credentials_path": None,
|
|
945
|
+
"credentials_json": None,
|
|
946
|
+
"credentials_json_b64": None,
|
|
947
|
+
"multitenant": False,
|
|
948
|
+
},
|
|
949
|
+
),
|
|
950
|
+
],
|
|
951
|
+
)
|
|
952
|
+
def test_add_storage_adapter_generates_loadable_config_only(
|
|
953
|
+
tmp_path: Path,
|
|
954
|
+
adapter: str,
|
|
955
|
+
params: dict[str, str],
|
|
956
|
+
expected: dict[str, object],
|
|
957
|
+
) -> None:
|
|
958
|
+
project_dir = _minimal_project(tmp_path)
|
|
959
|
+
config_path = project_dir / "config" / "adapters" / "outbound" / "storage.yaml"
|
|
960
|
+
adapter_params = dict(params)
|
|
961
|
+
expected_config = dict(expected)
|
|
962
|
+
if adapter == "filesystem":
|
|
963
|
+
root_path = project_dir / "files"
|
|
964
|
+
adapter_params["root_path"] = str(root_path)
|
|
965
|
+
expected_config["root_path"] = str(root_path)
|
|
966
|
+
|
|
967
|
+
add_adapter_cmd(
|
|
968
|
+
project_dir=project_dir,
|
|
969
|
+
capability_name="storage",
|
|
970
|
+
adapter=adapter,
|
|
971
|
+
adapter_params=adapter_params,
|
|
972
|
+
yes=True,
|
|
973
|
+
)
|
|
974
|
+
config = yaml.safe_load(config_path.read_text(encoding="utf-8"))
|
|
975
|
+
|
|
976
|
+
assert config == expected_config
|
|
977
|
+
assert "storage:" not in (project_dir / "config" / "adapters" / "adapters.yaml").read_text(
|
|
978
|
+
encoding="utf-8"
|
|
979
|
+
)
|
|
980
|
+
if adapter == "azure-blob":
|
|
981
|
+
secrets = yaml.safe_load(
|
|
982
|
+
(project_dir / "config" / "secrets.yaml").read_text(encoding="utf-8")
|
|
983
|
+
)
|
|
984
|
+
assert secrets["resolver"] == "env"
|
|
985
|
+
assert secrets["mappings"] == {
|
|
986
|
+
"adapters.storage.connection_string": "AZURE_STORAGE_CONNECTION_STRING",
|
|
987
|
+
"adapters.storage.account_key": "AZURE_STORAGE_ACCOUNT_KEY",
|
|
988
|
+
"adapters.storage.sas_token": "AZURE_STORAGE_SAS_TOKEN",
|
|
989
|
+
}
|
|
990
|
+
package_root = project_dir / "src" / "demo_service"
|
|
991
|
+
assert not (package_root / "adapters" / "outbound" / adapter).exists()
|
|
992
|
+
|
|
993
|
+
from arclith import Arclith
|
|
994
|
+
|
|
995
|
+
arclith = Arclith(project_dir / "config")
|
|
996
|
+
|
|
997
|
+
assert arclith.config.adapters.storage is not None
|
|
998
|
+
assert arclith.config.adapters.storage.adapter == adapter
|
|
999
|
+
assert arclith.config.adapters.storage.prefix == "uploads"
|
|
1000
|
+
if adapter == "filesystem":
|
|
1001
|
+
from arclith.adapters.outbound.filesystem import FilesystemFileStorage
|
|
1002
|
+
|
|
1003
|
+
assert isinstance(arclith.file_storage(), FilesystemFileStorage)
|
|
1004
|
+
|
|
1005
|
+
|
|
871
1006
|
def test_add_openai_llm_adapter_generates_secret_mapping(
|
|
872
1007
|
tmp_path: Path,
|
|
873
1008
|
monkeypatch: pytest.MonkeyPatch,
|
|
@@ -1147,7 +1282,7 @@ def test_add_langgraph_agent_adapter_generates_runtime_entrypoint(tmp_path: Path
|
|
|
1147
1282
|
project_dir=project_dir,
|
|
1148
1283
|
capability_name="agent",
|
|
1149
1284
|
adapter="langgraph",
|
|
1150
|
-
adapter_params={"graph_name": "todo_agent"},
|
|
1285
|
+
adapter_params={"graph_name": "todo_agent", "stream_mode": "updates,custom"},
|
|
1151
1286
|
yes=True,
|
|
1152
1287
|
)
|
|
1153
1288
|
|
|
@@ -1173,8 +1308,10 @@ def test_add_langgraph_agent_adapter_generates_runtime_entrypoint(tmp_path: Path
|
|
|
1173
1308
|
assert "agent:" not in adapters_yaml
|
|
1174
1309
|
assert 'name: "todo_agent"' in langgraph_config
|
|
1175
1310
|
assert 'entrypoint: "./src/demo_service/adapters/inbound/langgraph/agent.py:agent"' in langgraph_config
|
|
1311
|
+
assert "stream_mode: [updates, custom]" in langgraph_config
|
|
1176
1312
|
generated_agent = agent_file.read_text(encoding="utf-8")
|
|
1177
1313
|
assert "Template minimal volontaire" in generated_agent
|
|
1314
|
+
assert "get_stream_writer" in generated_agent
|
|
1178
1315
|
assert "agent = arclith.langgraph(AgentState, register_agent, name=\"todo_agent\")" in generated_agent
|
|
1179
1316
|
assert not (package_root / "adapters" / "outbound" / "langgraph").exists()
|
|
1180
1317
|
|
|
@@ -1209,6 +1346,10 @@ async def test_add_langgraph_agent_adapter_generates_compilable_minimal_agent(
|
|
|
1209
1346
|
try:
|
|
1210
1347
|
spec.loader.exec_module(module)
|
|
1211
1348
|
assert await module.agent.ainvoke({"messages": []}) == {"messages": []}
|
|
1349
|
+
events = [event async for event in module.agent.astream({"messages": []}, stream_mode="custom")]
|
|
1350
|
+
assert events == [
|
|
1351
|
+
{"kind": "progress", "stage": "agent.started", "message": "Agent node started."}
|
|
1352
|
+
]
|
|
1212
1353
|
finally:
|
|
1213
1354
|
sys.modules.pop(spec.name, None)
|
|
1214
1355
|
|
|
@@ -393,7 +393,7 @@ def test_agent_capability_catalog_declares_langgraph() -> None:
|
|
|
393
393
|
"{package_path}/adapters/inbound/langgraph/__init__.py",
|
|
394
394
|
"{package_path}/adapters/inbound/langgraph/agent.py",
|
|
395
395
|
]
|
|
396
|
-
assert [parameter.name for parameter in langgraph.parameters] == ["graph_name"]
|
|
396
|
+
assert [parameter.name for parameter in langgraph.parameters] == ["graph_name", "stream_mode"]
|
|
397
397
|
|
|
398
398
|
|
|
399
399
|
def test_repository_adapter_specs_include_config_and_parameters() -> None:
|
|
@@ -432,6 +432,67 @@ def test_repository_adapter_specs_include_config_and_parameters() -> None:
|
|
|
432
432
|
]
|
|
433
433
|
|
|
434
434
|
|
|
435
|
+
def test_storage_capability_catalog_declares_object_storage_adapters() -> None:
|
|
436
|
+
capability = get_capability("storage")
|
|
437
|
+
|
|
438
|
+
assert capability is not None
|
|
439
|
+
assert capability.layer == "outbound"
|
|
440
|
+
assert capability.activation_config_key is None
|
|
441
|
+
assert capability.adapter_names() == ("filesystem", "s3", "azure-blob", "gcs")
|
|
442
|
+
|
|
443
|
+
filesystem = capability.get_adapter("filesystem")
|
|
444
|
+
s3 = capability.get_adapter("s3")
|
|
445
|
+
azure_blob = capability.get_adapter("azure-blob")
|
|
446
|
+
gcs = capability.get_adapter("gcs")
|
|
447
|
+
|
|
448
|
+
assert filesystem is not None
|
|
449
|
+
assert filesystem.config_path == "config/adapters/outbound/storage.yaml"
|
|
450
|
+
assert filesystem.entity_scoped is False
|
|
451
|
+
assert [parameter.name for parameter in filesystem.parameters] == [
|
|
452
|
+
"root_path",
|
|
453
|
+
"prefix",
|
|
454
|
+
"create_root",
|
|
455
|
+
"multitenant",
|
|
456
|
+
]
|
|
457
|
+
assert s3 is not None
|
|
458
|
+
assert s3.config_path == "config/adapters/outbound/storage.yaml"
|
|
459
|
+
assert [parameter.name for parameter in s3.parameters] == [
|
|
460
|
+
"bucket_name",
|
|
461
|
+
"prefix",
|
|
462
|
+
"region_name",
|
|
463
|
+
"endpoint_url",
|
|
464
|
+
"force_path_style",
|
|
465
|
+
"multitenant",
|
|
466
|
+
]
|
|
467
|
+
assert azure_blob is not None
|
|
468
|
+
assert azure_blob.config_path == "config/adapters/outbound/storage.yaml"
|
|
469
|
+
assert [parameter.name for parameter in azure_blob.parameters] == [
|
|
470
|
+
"account_url",
|
|
471
|
+
"container_name",
|
|
472
|
+
"prefix",
|
|
473
|
+
"use_default_credential",
|
|
474
|
+
"multitenant",
|
|
475
|
+
]
|
|
476
|
+
assert [mapping.field_path for mapping in azure_blob.secret_mappings] == [
|
|
477
|
+
"adapters.storage.connection_string",
|
|
478
|
+
"adapters.storage.account_key",
|
|
479
|
+
"adapters.storage.sas_token",
|
|
480
|
+
]
|
|
481
|
+
assert [mapping.secret_key for mapping in azure_blob.secret_mappings] == [
|
|
482
|
+
"AZURE_STORAGE_CONNECTION_STRING",
|
|
483
|
+
"AZURE_STORAGE_ACCOUNT_KEY",
|
|
484
|
+
"AZURE_STORAGE_SAS_TOKEN",
|
|
485
|
+
]
|
|
486
|
+
assert gcs is not None
|
|
487
|
+
assert gcs.config_path == "config/adapters/outbound/storage.yaml"
|
|
488
|
+
assert [parameter.name for parameter in gcs.parameters] == [
|
|
489
|
+
"bucket_name",
|
|
490
|
+
"prefix",
|
|
491
|
+
"project_id",
|
|
492
|
+
"multitenant",
|
|
493
|
+
]
|
|
494
|
+
|
|
495
|
+
|
|
435
496
|
def test_capability_catalog_is_json_serializable() -> None:
|
|
436
497
|
payload = capability_catalog_as_dict()
|
|
437
498
|
|
|
@@ -439,6 +500,10 @@ def test_capability_catalog_is_json_serializable() -> None:
|
|
|
439
500
|
|
|
440
501
|
assert "repository" in encoded
|
|
441
502
|
assert "mongodb" in encoded
|
|
503
|
+
assert "storage" in encoded
|
|
504
|
+
assert "filesystem" in encoded
|
|
505
|
+
assert "azure-blob" in encoded
|
|
506
|
+
assert "gcs" in encoded
|
|
442
507
|
assert "cache" in encoded
|
|
443
508
|
assert "redis" in encoded
|
|
444
509
|
assert "logger" in encoded
|
|
@@ -501,6 +566,48 @@ def test_capabilities_command_outputs_json_catalog() -> None:
|
|
|
501
566
|
"MARIADB_URL",
|
|
502
567
|
"MARIADB_PASSWORD",
|
|
503
568
|
]
|
|
569
|
+
storage = payload_by_name["storage"]
|
|
570
|
+
assert storage["layer"] == "outbound"
|
|
571
|
+
assert storage["activation_config_key"] is None
|
|
572
|
+
assert [adapter["name"] for adapter in storage["adapters"]] == [
|
|
573
|
+
"filesystem",
|
|
574
|
+
"s3",
|
|
575
|
+
"azure-blob",
|
|
576
|
+
"gcs",
|
|
577
|
+
]
|
|
578
|
+
filesystem = storage["adapters"][0]
|
|
579
|
+
assert filesystem["config_path"] == "config/adapters/outbound/storage.yaml"
|
|
580
|
+
assert filesystem["entity_scoped"] is False
|
|
581
|
+
assert [parameter["name"] for parameter in filesystem["parameters"]] == [
|
|
582
|
+
"root_path",
|
|
583
|
+
"prefix",
|
|
584
|
+
"create_root",
|
|
585
|
+
"multitenant",
|
|
586
|
+
]
|
|
587
|
+
s3 = storage["adapters"][1]
|
|
588
|
+
assert s3["config_path"] == "config/adapters/outbound/storage.yaml"
|
|
589
|
+
assert [parameter["name"] for parameter in s3["parameters"]] == [
|
|
590
|
+
"bucket_name",
|
|
591
|
+
"prefix",
|
|
592
|
+
"region_name",
|
|
593
|
+
"endpoint_url",
|
|
594
|
+
"force_path_style",
|
|
595
|
+
"multitenant",
|
|
596
|
+
]
|
|
597
|
+
azure_blob = storage["adapters"][2]
|
|
598
|
+
assert azure_blob["config_path"] == "config/adapters/outbound/storage.yaml"
|
|
599
|
+
assert [parameter["name"] for parameter in azure_blob["parameters"]] == [
|
|
600
|
+
"account_url",
|
|
601
|
+
"container_name",
|
|
602
|
+
"prefix",
|
|
603
|
+
"use_default_credential",
|
|
604
|
+
"multitenant",
|
|
605
|
+
]
|
|
606
|
+
assert [mapping["field_path"] for mapping in azure_blob["secret_mappings"]] == [
|
|
607
|
+
"adapters.storage.connection_string",
|
|
608
|
+
"adapters.storage.account_key",
|
|
609
|
+
"adapters.storage.sas_token",
|
|
610
|
+
]
|
|
504
611
|
cache = payload_by_name["cache"]
|
|
505
612
|
assert [adapter["name"] for adapter in cache["adapters"]] == ["memory", "redis"]
|
|
506
613
|
assert cache["adapters"][0]["capability"] == "cache"
|
|
@@ -52,7 +52,7 @@ def test_init_project_creates_minimal_src_layout_without_entity(tmp_path: Path)
|
|
|
52
52
|
"observability:\n"
|
|
53
53
|
" enabled: []\n"
|
|
54
54
|
)
|
|
55
|
-
assert "arclith[fastapi,mcp]>=0.
|
|
55
|
+
assert "arclith[fastapi,mcp]>=0.17.0" in (generated / "pyproject.toml").read_text(encoding="utf-8")
|
|
56
56
|
assert (package_root / "domain" / "models" / "__init__.py").exists()
|
|
57
57
|
assert (package_root / "domain" / "ports" / "inbound" / "__init__.py").exists()
|
|
58
58
|
assert (package_root / "domain" / "ports" / "outbound" / "__init__.py").exists()
|
|
@@ -46,7 +46,7 @@ wheels = [
|
|
|
46
46
|
|
|
47
47
|
[[package]]
|
|
48
48
|
name = "arclith"
|
|
49
|
-
version = "0.
|
|
49
|
+
version = "0.17.0"
|
|
50
50
|
source = { editable = "../" }
|
|
51
51
|
dependencies = [
|
|
52
52
|
{ name = "fastapi" },
|
|
@@ -63,6 +63,12 @@ requires-dist = [
|
|
|
63
63
|
{ name = "aio-pika", marker = "extra == 'rabbitmq'", specifier = ">=10.0.1,<11.0.0" },
|
|
64
64
|
{ name = "asyncmy", marker = "extra == 'all'", specifier = ">=0.2.10" },
|
|
65
65
|
{ name = "asyncmy", marker = "extra == 'mariadb'", specifier = ">=0.2.10" },
|
|
66
|
+
{ name = "azure-identity", marker = "extra == 'all'", specifier = ">=1.25.1,<2.0.0" },
|
|
67
|
+
{ name = "azure-identity", marker = "extra == 'azure-blob'", specifier = ">=1.25.1,<2.0.0" },
|
|
68
|
+
{ name = "azure-storage-blob", marker = "extra == 'all'", specifier = ">=12.27.1,<13.0.0" },
|
|
69
|
+
{ name = "azure-storage-blob", marker = "extra == 'azure-blob'", specifier = ">=12.27.1,<13.0.0" },
|
|
70
|
+
{ name = "boto3", marker = "extra == 'all'", specifier = ">=1.43.70" },
|
|
71
|
+
{ name = "boto3", marker = "extra == 's3'", specifier = ">=1.43.70" },
|
|
66
72
|
{ name = "duckdb", marker = "extra == 'all'", specifier = "==1.5.0" },
|
|
67
73
|
{ name = "duckdb", marker = "extra == 'duckdb'", specifier = "==1.5.0" },
|
|
68
74
|
{ name = "fastapi", specifier = ">=0.135.1" },
|
|
@@ -71,6 +77,8 @@ requires-dist = [
|
|
|
71
77
|
{ name = "fastmcp", specifier = ">=3.1.0" },
|
|
72
78
|
{ name = "fastmcp", marker = "extra == 'all'", specifier = "==3.1.0" },
|
|
73
79
|
{ name = "fastmcp", marker = "extra == 'mcp'", specifier = "==3.1.0" },
|
|
80
|
+
{ name = "google-cloud-storage", marker = "extra == 'all'", specifier = ">=3.3.0,<4.0.0" },
|
|
81
|
+
{ name = "google-cloud-storage", marker = "extra == 'gcs'", specifier = ">=3.3.0,<4.0.0" },
|
|
74
82
|
{ name = "httpx", marker = "extra == 'all'", specifier = ">=0.27.0" },
|
|
75
83
|
{ name = "httpx", marker = "extra == 'auth'", specifier = ">=0.27.0" },
|
|
76
84
|
{ name = "hvac", marker = "extra == 'all'", specifier = "==2.3.0" },
|
|
@@ -112,7 +120,7 @@ requires-dist = [
|
|
|
112
120
|
{ name = "uvicorn", marker = "extra == 'all'", specifier = "==0.41.0" },
|
|
113
121
|
{ name = "uvicorn", marker = "extra == 'fastapi'", specifier = "==0.41.0" },
|
|
114
122
|
]
|
|
115
|
-
provides-extras = ["mongodb", "duckdb", "mariadb", "fastapi", "mcp", "vault", "cache", "auth", "rabbitmq", "langgraph", "opentelemetry", "all"]
|
|
123
|
+
provides-extras = ["mongodb", "duckdb", "mariadb", "fastapi", "mcp", "vault", "cache", "auth", "rabbitmq", "langgraph", "opentelemetry", "all", "azure-blob", "s3", "gcs"]
|
|
116
124
|
|
|
117
125
|
[package.metadata.requires-dev]
|
|
118
126
|
dev = [
|
|
@@ -134,7 +142,7 @@ docs = [
|
|
|
134
142
|
|
|
135
143
|
[[package]]
|
|
136
144
|
name = "arclith-cli"
|
|
137
|
-
version = "0.
|
|
145
|
+
version = "0.14.0"
|
|
138
146
|
source = { editable = "." }
|
|
139
147
|
dependencies = [
|
|
140
148
|
{ name = "arclith" },
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.13.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|