langchain-trigger-server 0.2.7__tar.gz → 0.2.8__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.

Files changed (28) hide show
  1. langchain_trigger_server-0.2.8/.github/workflows/release.yml +124 -0
  2. {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.8}/PKG-INFO +1 -1
  3. {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.8}/langchain_triggers/app.py +7 -1
  4. {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.8}/langchain_triggers/core.py +7 -0
  5. {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.8}/pyproject.toml +1 -1
  6. {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.8}/tests/unit/test_trigger_server_api.py +95 -1
  7. {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.8}/uv.lock +1 -1
  8. langchain_trigger_server-0.2.8/version_comparison.txt +1 -0
  9. langchain_trigger_server-0.2.7/.github/workflows/release.yml +0 -38
  10. {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.8}/.github/actions/uv_setup/action.yml +0 -0
  11. {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.8}/.github/workflows/_lint.yml +0 -0
  12. {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.8}/.github/workflows/_test.yml +0 -0
  13. {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.8}/.github/workflows/ci.yml +0 -0
  14. {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.8}/.gitignore +0 -0
  15. {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.8}/Makefile +0 -0
  16. {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.8}/README.md +0 -0
  17. {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.8}/langchain_triggers/__init__.py +0 -0
  18. {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.8}/langchain_triggers/auth/__init__.py +0 -0
  19. {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.8}/langchain_triggers/auth/slack_hmac.py +0 -0
  20. {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.8}/langchain_triggers/cron_manager.py +0 -0
  21. {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.8}/langchain_triggers/database/__init__.py +0 -0
  22. {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.8}/langchain_triggers/database/interface.py +0 -0
  23. {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.8}/langchain_triggers/database/supabase.py +0 -0
  24. {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.8}/langchain_triggers/decorators.py +0 -0
  25. {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.8}/langchain_triggers/triggers/__init__.py +0 -0
  26. {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.8}/langchain_triggers/triggers/cron_trigger.py +0 -0
  27. {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.8}/tests/__init__.py +0 -0
  28. {langchain_trigger_server-0.2.7 → langchain_trigger_server-0.2.8}/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.7
3
+ Version: 0.2.8
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
@@ -323,6 +323,7 @@ class TriggerServer:
323
323
  raise HTTPException(
324
324
  status_code=400, detail=f"Unknown trigger type: {trigger_id}"
325
325
  )
326
+ client_metadata = payload.pop("metadata", None)
326
327
 
327
328
  # Parse payload into registration model first
328
329
  try:
@@ -368,11 +369,16 @@ class TriggerServer:
368
369
 
369
370
  resource_dict = registration_instance.model_dump()
370
371
 
372
+ merged_metadata = {}
373
+ if client_metadata:
374
+ merged_metadata["client_metadata"] = client_metadata
375
+ merged_metadata.update(result.metadata)
376
+
371
377
  registration = await self.database.create_trigger_registration(
372
378
  user_id=user_id,
373
379
  template_id=trigger.id,
374
380
  resource=resource_dict,
375
- metadata=result.metadata,
381
+ metadata=merged_metadata,
376
382
  )
377
383
 
378
384
  if not registration:
@@ -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
  ):
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "langchain-trigger-server"
7
- version = "0.2.7"
7
+ version = "0.2.8"
8
8
  description = "Generic event-driven triggers framework"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -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"]
@@ -393,7 +393,7 @@ wheels = [
393
393
 
394
394
  [[package]]
395
395
  name = "langchain-trigger-server"
396
- version = "0.2.6rc8"
396
+ version = "0.2.8"
397
397
  source = { editable = "." }
398
398
  dependencies = [
399
399
  { name = "apscheduler" },
@@ -0,0 +1 @@
1
+ Current: 0.2.8, PyPI: 0.2.7, 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