langchain-trigger-server 0.2.7__tar.gz → 0.2.9__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.
Potentially problematic release.
This version of langchain-trigger-server might be problematic. Click here for more details.
- langchain_trigger_server-0.2.9/.github/workflows/release.yml +124 -0
- {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/PKG-INFO +1 -1
- {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/langchain_triggers/app.py +51 -16
- {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/langchain_triggers/core.py +7 -0
- {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/langchain_triggers/cron_manager.py +5 -0
- {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/pyproject.toml +1 -1
- {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/tests/unit/test_trigger_server_api.py +95 -1
- {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/uv.lock +1 -1
- langchain_trigger_server-0.2.9/version_comparison.txt +1 -0
- langchain_trigger_server-0.2.7/.github/workflows/release.yml +0 -38
- {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/.github/actions/uv_setup/action.yml +0 -0
- {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/.github/workflows/_lint.yml +0 -0
- {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/.github/workflows/_test.yml +0 -0
- {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/.github/workflows/ci.yml +0 -0
- {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/.gitignore +0 -0
- {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/Makefile +0 -0
- {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/README.md +0 -0
- {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/langchain_triggers/__init__.py +0 -0
- {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/langchain_triggers/auth/__init__.py +0 -0
- {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/langchain_triggers/auth/slack_hmac.py +0 -0
- {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/langchain_triggers/database/__init__.py +0 -0
- {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/langchain_triggers/database/interface.py +0 -0
- {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/langchain_triggers/database/supabase.py +0 -0
- {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/langchain_triggers/decorators.py +0 -0
- {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/langchain_triggers/triggers/__init__.py +0 -0
- {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/langchain_triggers/triggers/cron_trigger.py +0 -0
- {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/tests/__init__.py +0 -0
- {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/tests/unit/__init__.py +0 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
name: Build and publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main ]
|
|
6
|
+
release:
|
|
7
|
+
types: [published]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
env:
|
|
11
|
+
PYTHON_VERSION: "3.11"
|
|
12
|
+
UV_FROZEN: "true"
|
|
13
|
+
UV_NO_SYNC: "true"
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
# First, run lint and test to ensure quality
|
|
17
|
+
lint-and-test:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
strategy:
|
|
20
|
+
matrix:
|
|
21
|
+
python-version: ["3.11", "3.12"]
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
|
|
25
|
+
- name: Set up Python + uv
|
|
26
|
+
uses: "./.github/actions/uv_setup"
|
|
27
|
+
with:
|
|
28
|
+
python-version: ${{ matrix.python-version }}
|
|
29
|
+
|
|
30
|
+
- name: Install dependencies
|
|
31
|
+
run: uv sync --group dev
|
|
32
|
+
|
|
33
|
+
- name: Run lint
|
|
34
|
+
run: uv run ruff check .
|
|
35
|
+
|
|
36
|
+
- name: Run tests
|
|
37
|
+
run: uv run pytest tests/
|
|
38
|
+
|
|
39
|
+
# Check version and publish package
|
|
40
|
+
build-and-publish:
|
|
41
|
+
needs: lint-and-test
|
|
42
|
+
name: Build and publish Python distribution to PyPI
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
environment:
|
|
45
|
+
name: pypi
|
|
46
|
+
url: https://pypi.org/p/langchain-trigger-server
|
|
47
|
+
permissions:
|
|
48
|
+
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
|
|
49
|
+
contents: read # Required for private repository access
|
|
50
|
+
|
|
51
|
+
steps:
|
|
52
|
+
- uses: actions/checkout@v4
|
|
53
|
+
with:
|
|
54
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
55
|
+
|
|
56
|
+
- name: Set up Python + uv
|
|
57
|
+
uses: "./.github/actions/uv_setup"
|
|
58
|
+
with:
|
|
59
|
+
python-version: ${{ env.PYTHON_VERSION }}
|
|
60
|
+
|
|
61
|
+
- name: Get current version from pyproject.toml
|
|
62
|
+
id: get-version
|
|
63
|
+
run: |
|
|
64
|
+
VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
|
|
65
|
+
echo "current_version=$VERSION" >> $GITHUB_OUTPUT
|
|
66
|
+
echo "Current version: $VERSION"
|
|
67
|
+
|
|
68
|
+
- name: Get latest PyPI version
|
|
69
|
+
id: get-pypi-version
|
|
70
|
+
run: |
|
|
71
|
+
PYPI_VERSION=$(python -c "
|
|
72
|
+
import json, urllib.request, urllib.error
|
|
73
|
+
try:
|
|
74
|
+
with urllib.request.urlopen('https://pypi.org/pypi/langchain-trigger-server/json') as response:
|
|
75
|
+
data = json.loads(response.read())
|
|
76
|
+
print(data['info']['version'])
|
|
77
|
+
except urllib.error.HTTPError as e:
|
|
78
|
+
if e.code == 404:
|
|
79
|
+
print('0.0.0') # Package doesn't exist yet
|
|
80
|
+
else:
|
|
81
|
+
raise
|
|
82
|
+
")
|
|
83
|
+
echo "pypi_version=$PYPI_VERSION" >> $GITHUB_OUTPUT
|
|
84
|
+
echo "PyPI version: $PYPI_VERSION"
|
|
85
|
+
|
|
86
|
+
- name: Compare versions
|
|
87
|
+
id: compare-versions
|
|
88
|
+
run: |
|
|
89
|
+
python -c "
|
|
90
|
+
from distutils.version import LooseVersion
|
|
91
|
+
current = LooseVersion('${{ steps.get-version.outputs.current_version }}')
|
|
92
|
+
pypi = LooseVersion('${{ steps.get-pypi-version.outputs.pypi_version }}')
|
|
93
|
+
should_publish = current > pypi
|
|
94
|
+
print(f'Current: {current}, PyPI: {pypi}, Should publish: {should_publish}')
|
|
95
|
+
" > version_comparison.txt
|
|
96
|
+
cat version_comparison.txt
|
|
97
|
+
|
|
98
|
+
SHOULD_PUBLISH=$(python -c "
|
|
99
|
+
from distutils.version import LooseVersion
|
|
100
|
+
current = LooseVersion('${{ steps.get-version.outputs.current_version }}')
|
|
101
|
+
pypi = LooseVersion('${{ steps.get-pypi-version.outputs.pypi_version }}')
|
|
102
|
+
print('true' if current > pypi else 'false')
|
|
103
|
+
")
|
|
104
|
+
echo "should_publish=$SHOULD_PUBLISH" >> $GITHUB_OUTPUT
|
|
105
|
+
|
|
106
|
+
- name: Build package
|
|
107
|
+
if: steps.compare-versions.outputs.should_publish == 'true'
|
|
108
|
+
run: uv build
|
|
109
|
+
|
|
110
|
+
- name: Publish package distributions to PyPI
|
|
111
|
+
if: steps.compare-versions.outputs.should_publish == 'true'
|
|
112
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
113
|
+
with:
|
|
114
|
+
verbose: true
|
|
115
|
+
print-hash: true
|
|
116
|
+
attestations: false
|
|
117
|
+
|
|
118
|
+
- name: Create summary
|
|
119
|
+
run: |
|
|
120
|
+
if [ "${{ steps.compare-versions.outputs.should_publish }}" = "true" ]; then
|
|
121
|
+
echo "✅ Published langchain-trigger-server v${{ steps.get-version.outputs.current_version }} to PyPI" >> $GITHUB_STEP_SUMMARY
|
|
122
|
+
else
|
|
123
|
+
echo "⏭️ Skipped langchain-trigger-server: v${{ steps.get-version.outputs.current_version }} not newer than PyPI v${{ steps.get-pypi-version.outputs.pypi_version }}" >> $GITHUB_STEP_SUMMARY
|
|
124
|
+
fi
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langchain-trigger-server
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.9
|
|
4
4
|
Summary: Generic event-driven triggers framework
|
|
5
5
|
Project-URL: Homepage, https://github.com/langchain-ai/open-agent-platform
|
|
6
6
|
Project-URL: Repository, https://github.com/langchain-ai/open-agent-platform
|
|
@@ -5,8 +5,10 @@ from __future__ import annotations
|
|
|
5
5
|
import logging
|
|
6
6
|
import os
|
|
7
7
|
from collections.abc import Callable
|
|
8
|
+
from datetime import UTC, datetime, timedelta
|
|
8
9
|
from typing import Any
|
|
9
10
|
|
|
11
|
+
import jwt
|
|
10
12
|
from fastapi import Depends, FastAPI, HTTPException, Request
|
|
11
13
|
from langchain_auth.client import Client
|
|
12
14
|
from langgraph_sdk import get_client
|
|
@@ -27,6 +29,28 @@ from .triggers.cron_trigger import CRON_TRIGGER_ID
|
|
|
27
29
|
logger = logging.getLogger(__name__)
|
|
28
30
|
|
|
29
31
|
|
|
32
|
+
def get_x_service_jwt_token(
|
|
33
|
+
payload: dict[str, Any] | None = None, expiration_seconds: int = 60 * 60
|
|
34
|
+
) -> str:
|
|
35
|
+
exp_datetime = datetime.now(tz=UTC) + timedelta(seconds=expiration_seconds)
|
|
36
|
+
exp = int(exp_datetime.timestamp())
|
|
37
|
+
|
|
38
|
+
payload = payload or {}
|
|
39
|
+
payload = {
|
|
40
|
+
"sub": "unspecified",
|
|
41
|
+
"exp": exp,
|
|
42
|
+
**payload,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
secret = os.environ["X_SERVICE_AUTH_JWT_SECRET"]
|
|
46
|
+
|
|
47
|
+
return jwt.encode(
|
|
48
|
+
payload,
|
|
49
|
+
secret,
|
|
50
|
+
algorithm="HS256",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
30
54
|
class AuthenticationMiddleware(BaseHTTPMiddleware):
|
|
31
55
|
"""Middleware to handle authentication for API endpoints."""
|
|
32
56
|
|
|
@@ -106,7 +130,6 @@ class TriggerServer:
|
|
|
106
130
|
|
|
107
131
|
# LangGraph configuration
|
|
108
132
|
self.langgraph_api_url = os.getenv("LANGGRAPH_API_URL")
|
|
109
|
-
self.langsmith_api_key = os.getenv("LANGCHAIN_API_KEY")
|
|
110
133
|
self.trigger_server_auth_api_url = os.getenv("TRIGGER_SERVER_HOST_API_URL")
|
|
111
134
|
|
|
112
135
|
if not self.langgraph_api_url:
|
|
@@ -115,16 +138,10 @@ class TriggerServer:
|
|
|
115
138
|
self.langgraph_api_url = self.langgraph_api_url.rstrip("/")
|
|
116
139
|
|
|
117
140
|
# Initialize LangGraph SDK client
|
|
118
|
-
self.langgraph_client = get_client(
|
|
119
|
-
url=self.langgraph_api_url, api_key=self.langsmith_api_key
|
|
120
|
-
)
|
|
141
|
+
self.langgraph_client = get_client(url=self.langgraph_api_url, api_key=None)
|
|
121
142
|
logger.info(
|
|
122
143
|
f"✓ LangGraph client initialized with URL: {self.langgraph_api_url}"
|
|
123
144
|
)
|
|
124
|
-
if self.langsmith_api_key:
|
|
125
|
-
logger.info("✓ LangGraph client initialized with API key.")
|
|
126
|
-
else:
|
|
127
|
-
logger.warning("⚠ LangGraph client initialized without API key")
|
|
128
145
|
|
|
129
146
|
# Initialize LangChain auth client
|
|
130
147
|
langchain_api_key = os.getenv("LANGCHAIN_API_KEY")
|
|
@@ -323,6 +340,7 @@ class TriggerServer:
|
|
|
323
340
|
raise HTTPException(
|
|
324
341
|
status_code=400, detail=f"Unknown trigger type: {trigger_id}"
|
|
325
342
|
)
|
|
343
|
+
client_metadata = payload.pop("metadata", None)
|
|
326
344
|
|
|
327
345
|
# Parse payload into registration model first
|
|
328
346
|
try:
|
|
@@ -368,11 +386,16 @@ class TriggerServer:
|
|
|
368
386
|
|
|
369
387
|
resource_dict = registration_instance.model_dump()
|
|
370
388
|
|
|
389
|
+
merged_metadata = {}
|
|
390
|
+
if client_metadata:
|
|
391
|
+
merged_metadata["client_metadata"] = client_metadata
|
|
392
|
+
merged_metadata.update(result.metadata)
|
|
393
|
+
|
|
371
394
|
registration = await self.database.create_trigger_registration(
|
|
372
395
|
user_id=user_id,
|
|
373
396
|
template_id=trigger.id,
|
|
374
397
|
resource=resource_dict,
|
|
375
|
-
metadata=
|
|
398
|
+
metadata=merged_metadata,
|
|
376
399
|
)
|
|
377
400
|
|
|
378
401
|
if not registration:
|
|
@@ -579,6 +602,7 @@ class TriggerServer:
|
|
|
579
602
|
body_bytes = await request.body()
|
|
580
603
|
body_str = body_bytes.decode("utf-8")
|
|
581
604
|
|
|
605
|
+
# TODO(sam/palash): We should not have API specific things in this framework repo. We should clean this up.
|
|
582
606
|
if self._is_slack_trigger(trigger):
|
|
583
607
|
await self._verify_slack_webhook_auth_with_body(
|
|
584
608
|
request, body_str
|
|
@@ -623,6 +647,11 @@ class TriggerServer:
|
|
|
623
647
|
# Ensure agent_id and user_id are strings for JSON serialization
|
|
624
648
|
agent_id_str = str(agent_id)
|
|
625
649
|
user_id_str = str(result.registration["user_id"])
|
|
650
|
+
tenant_id_str = str(
|
|
651
|
+
result.registration.get("metadata", {})
|
|
652
|
+
.get("client_metadata", {})
|
|
653
|
+
.get("tenant_id")
|
|
654
|
+
)
|
|
626
655
|
|
|
627
656
|
agent_input = {"messages": [{"role": "human", "content": message}]}
|
|
628
657
|
|
|
@@ -630,6 +659,7 @@ class TriggerServer:
|
|
|
630
659
|
success = await self._invoke_agent(
|
|
631
660
|
agent_id=agent_id_str,
|
|
632
661
|
user_id=user_id_str,
|
|
662
|
+
tenant_id=tenant_id_str,
|
|
633
663
|
input_data=agent_input,
|
|
634
664
|
)
|
|
635
665
|
if success:
|
|
@@ -656,6 +686,7 @@ class TriggerServer:
|
|
|
656
686
|
self,
|
|
657
687
|
agent_id: str,
|
|
658
688
|
user_id: str,
|
|
689
|
+
tenant_id: str,
|
|
659
690
|
input_data: dict[str, Any],
|
|
660
691
|
) -> bool:
|
|
661
692
|
"""Invoke LangGraph agent using the SDK."""
|
|
@@ -665,20 +696,23 @@ class TriggerServer:
|
|
|
665
696
|
|
|
666
697
|
try:
|
|
667
698
|
headers = {
|
|
668
|
-
"x-
|
|
699
|
+
"x-api-key": "",
|
|
700
|
+
"x-auth-scheme": "langsmith-agent",
|
|
669
701
|
"x-user-id": user_id_str,
|
|
702
|
+
"x-tenant-id": tenant_id,
|
|
703
|
+
"x-service-key": get_x_service_jwt_token(
|
|
704
|
+
payload={
|
|
705
|
+
"tenant_id": tenant_id,
|
|
706
|
+
"user_id": user_id_str,
|
|
707
|
+
}
|
|
708
|
+
),
|
|
670
709
|
}
|
|
671
710
|
|
|
672
|
-
# Note: API key is already set in client initialization, no need to add to headers
|
|
673
|
-
if not self.langsmith_api_key:
|
|
674
|
-
logger.warning(
|
|
675
|
-
"No LANGSMITH_API_KEY available - authentication may fail"
|
|
676
|
-
)
|
|
677
|
-
|
|
678
711
|
thread = await self.langgraph_client.threads.create(
|
|
679
712
|
metadata={
|
|
680
713
|
"triggered_by": "langchain-triggers",
|
|
681
714
|
"user_id": user_id_str,
|
|
715
|
+
"tenant_id": tenant_id,
|
|
682
716
|
},
|
|
683
717
|
headers=headers,
|
|
684
718
|
)
|
|
@@ -691,6 +725,7 @@ class TriggerServer:
|
|
|
691
725
|
metadata={
|
|
692
726
|
"triggered_by": "langchain-triggers",
|
|
693
727
|
"user_id": user_id_str,
|
|
728
|
+
"tenant_id": tenant_id,
|
|
694
729
|
},
|
|
695
730
|
headers=headers,
|
|
696
731
|
)
|
{langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/langchain_triggers/core.py
RENAMED
|
@@ -88,6 +88,13 @@ class TriggerRegistrationResult(BaseModel):
|
|
|
88
88
|
"""Validate that required fields are provided based on create_registration."""
|
|
89
89
|
if self.create_registration and not self.metadata:
|
|
90
90
|
self.metadata = {} # Allow empty metadata for create_registration=True
|
|
91
|
+
|
|
92
|
+
if "client_metadata" in self.metadata:
|
|
93
|
+
raise ValueError(
|
|
94
|
+
"The 'client_metadata' key is reserved for client-provided metadata. "
|
|
95
|
+
"Registration handlers must not use this key in their metadata."
|
|
96
|
+
)
|
|
97
|
+
|
|
91
98
|
if not self.create_registration and (
|
|
92
99
|
not self.response_body or not self.status_code
|
|
93
100
|
):
|
{langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/langchain_triggers/cron_manager.py
RENAMED
|
@@ -202,6 +202,9 @@ class CronTriggerManager:
|
|
|
202
202
|
"""Execute a cron job - invoke agents. Can be called manually or by scheduler."""
|
|
203
203
|
registration_id = registration["id"]
|
|
204
204
|
user_id = registration["user_id"]
|
|
205
|
+
tenant_id = (
|
|
206
|
+
registration.get("metadata", {}).get("client_metadata", {}).get("tenant_id")
|
|
207
|
+
)
|
|
205
208
|
|
|
206
209
|
# Get agent links
|
|
207
210
|
agent_links = await self.trigger_server.database.get_agents_for_trigger(
|
|
@@ -222,6 +225,7 @@ class CronTriggerManager:
|
|
|
222
225
|
# Ensure agent_id and user_id are strings for JSON serialization
|
|
223
226
|
agent_id_str = str(agent_id)
|
|
224
227
|
user_id_str = str(user_id)
|
|
228
|
+
tenant_id_str = str(tenant_id)
|
|
225
229
|
|
|
226
230
|
current_time = datetime.utcnow()
|
|
227
231
|
current_time_str = current_time.strftime("%A, %B %d, %Y at %H:%M UTC")
|
|
@@ -239,6 +243,7 @@ class CronTriggerManager:
|
|
|
239
243
|
success = await self.trigger_server._invoke_agent(
|
|
240
244
|
agent_id=agent_id_str,
|
|
241
245
|
user_id=user_id_str,
|
|
246
|
+
tenant_id=tenant_id_str,
|
|
242
247
|
input_data=agent_input,
|
|
243
248
|
)
|
|
244
249
|
if success:
|
|
@@ -6,11 +6,11 @@ from unittest.mock import AsyncMock, patch
|
|
|
6
6
|
import pytest
|
|
7
7
|
import pytest_asyncio
|
|
8
8
|
from httpx import ASGITransport, AsyncClient
|
|
9
|
+
from pydantic import BaseModel
|
|
9
10
|
|
|
10
11
|
from langchain_triggers import TriggerServer
|
|
11
12
|
|
|
12
13
|
|
|
13
|
-
# Mock auth handler that returns a valid user
|
|
14
14
|
async def mock_auth_handler(request_body, headers):
|
|
15
15
|
"""Mock authentication handler for testing."""
|
|
16
16
|
auth_header = headers.get("authorization", "")
|
|
@@ -21,6 +21,17 @@ async def mock_auth_handler(request_body, headers):
|
|
|
21
21
|
return {"identity": f"test_user_{token}"}
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
class TestRegistration(BaseModel):
|
|
25
|
+
"""Simple registration model for testing."""
|
|
26
|
+
|
|
27
|
+
name: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def dummy_trigger_handler(payload, query_params, database, auth_client):
|
|
31
|
+
"""Dummy trigger handler for test triggers."""
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
|
|
24
35
|
# Mock database class
|
|
25
36
|
class MockDatabase:
|
|
26
37
|
"""Mock database for testing."""
|
|
@@ -354,3 +365,86 @@ async def test_user_isolation(trigger_server):
|
|
|
354
365
|
data = response.json()
|
|
355
366
|
assert len(data["data"]) == 1
|
|
356
367
|
assert data["data"][0]["user_id"] == "test_user_token2"
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
@pytest.mark.asyncio
|
|
371
|
+
async def test_client_metadata_storage(trigger_server):
|
|
372
|
+
"""Test that client-provided metadata is stored as client_metadata."""
|
|
373
|
+
from langchain_triggers import TriggerRegistrationResult, TriggerTemplate
|
|
374
|
+
|
|
375
|
+
async def test_registration_handler(user_id, auth_client, registration):
|
|
376
|
+
return TriggerRegistrationResult(metadata={"handler_data": "from_handler"})
|
|
377
|
+
|
|
378
|
+
test_trigger = TriggerTemplate(
|
|
379
|
+
id="test_metadata_trigger",
|
|
380
|
+
provider="test",
|
|
381
|
+
name="Test Metadata",
|
|
382
|
+
description="Tests metadata storage",
|
|
383
|
+
registration_model=TestRegistration,
|
|
384
|
+
registration_handler=test_registration_handler,
|
|
385
|
+
trigger_handler=dummy_trigger_handler,
|
|
386
|
+
)
|
|
387
|
+
trigger_server.add_trigger(test_trigger)
|
|
388
|
+
|
|
389
|
+
transport = ASGITransport(app=trigger_server.app, raise_app_exceptions=True)
|
|
390
|
+
async with AsyncClient(base_url="http://localhost", transport=transport) as client:
|
|
391
|
+
response = await client.post(
|
|
392
|
+
"/v1/triggers/registrations",
|
|
393
|
+
headers={"Authorization": "Bearer token1"},
|
|
394
|
+
json={
|
|
395
|
+
"type": "test_metadata_trigger",
|
|
396
|
+
"name": "test",
|
|
397
|
+
"metadata": {"tenant_id": "org-123"},
|
|
398
|
+
},
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
assert response.status_code == 200
|
|
402
|
+
data = response.json()
|
|
403
|
+
assert data["success"] is True
|
|
404
|
+
|
|
405
|
+
# Verify metadata in API response
|
|
406
|
+
registration_id = data["data"]["id"]
|
|
407
|
+
metadata = data["data"]["metadata"]
|
|
408
|
+
assert metadata["client_metadata"]["tenant_id"] == "org-123"
|
|
409
|
+
assert metadata["handler_data"] == "from_handler"
|
|
410
|
+
|
|
411
|
+
# Verify metadata persisted in database
|
|
412
|
+
db_registration = await trigger_server.database.get_trigger_registration(
|
|
413
|
+
registration_id, user_id="test_user_token1"
|
|
414
|
+
)
|
|
415
|
+
assert db_registration is not None
|
|
416
|
+
db_metadata = db_registration["metadata"]
|
|
417
|
+
assert db_metadata["client_metadata"]["tenant_id"] == "org-123"
|
|
418
|
+
assert db_metadata["handler_data"] == "from_handler"
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
@pytest.mark.asyncio
|
|
422
|
+
async def test_handler_cannot_use_client_metadata_key(trigger_server):
|
|
423
|
+
"""Test that handlers cannot use the reserved client_metadata key."""
|
|
424
|
+
from langchain_triggers import TriggerRegistrationResult, TriggerTemplate
|
|
425
|
+
|
|
426
|
+
async def bad_handler(user_id, auth_client, registration):
|
|
427
|
+
# Handler tries to use reserved key
|
|
428
|
+
return TriggerRegistrationResult(metadata={"client_metadata": "not allowed"})
|
|
429
|
+
|
|
430
|
+
test_trigger = TriggerTemplate(
|
|
431
|
+
id="test_bad_handler",
|
|
432
|
+
provider="test",
|
|
433
|
+
name="Test Bad Handler",
|
|
434
|
+
description="Tests validation",
|
|
435
|
+
registration_model=TestRegistration,
|
|
436
|
+
registration_handler=bad_handler,
|
|
437
|
+
trigger_handler=dummy_trigger_handler,
|
|
438
|
+
)
|
|
439
|
+
trigger_server.add_trigger(test_trigger)
|
|
440
|
+
|
|
441
|
+
transport = ASGITransport(app=trigger_server.app, raise_app_exceptions=True)
|
|
442
|
+
async with AsyncClient(base_url="http://localhost", transport=transport) as client:
|
|
443
|
+
response = await client.post(
|
|
444
|
+
"/v1/triggers/registrations",
|
|
445
|
+
headers={"Authorization": "Bearer token1"},
|
|
446
|
+
json={"type": "test_bad_handler", "name": "test"},
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
assert response.status_code == 500
|
|
450
|
+
assert "client_metadata" in response.json()["detail"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Current: 0.2.9, PyPI: 0.2.8, Should publish: True
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
name: Build and publish to PyPI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
release:
|
|
5
|
-
types: [published]
|
|
6
|
-
workflow_dispatch:
|
|
7
|
-
|
|
8
|
-
jobs:
|
|
9
|
-
build-and-publish:
|
|
10
|
-
name: Build and publish Python distribution to PyPI
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
environment:
|
|
13
|
-
name: pypi
|
|
14
|
-
url: https://pypi.org/p/langchain-trigger-server
|
|
15
|
-
permissions:
|
|
16
|
-
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
|
|
17
|
-
contents: read # Required for private repository access
|
|
18
|
-
|
|
19
|
-
steps:
|
|
20
|
-
- uses: actions/checkout@v4
|
|
21
|
-
with:
|
|
22
|
-
token: ${{ secrets.GITHUB_TOKEN }}
|
|
23
|
-
|
|
24
|
-
- name: Set up Python
|
|
25
|
-
uses: actions/setup-python@v4
|
|
26
|
-
with:
|
|
27
|
-
python-version: "3.11"
|
|
28
|
-
|
|
29
|
-
- name: Install build dependencies
|
|
30
|
-
run: |
|
|
31
|
-
python -m pip install --upgrade pip
|
|
32
|
-
python -m pip install build
|
|
33
|
-
|
|
34
|
-
- name: Build package
|
|
35
|
-
run: python -m build
|
|
36
|
-
|
|
37
|
-
- name: Publish package distributions to PyPI
|
|
38
|
-
uses: pypa/gh-action-pypi-publish@release/v1
|
|
File without changes
|
{langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/.github/workflows/_lint.yml
RENAMED
|
File without changes
|
{langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/.github/workflows/_test.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/langchain_triggers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.9}/langchain_triggers/decorators.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|