modulex-python 0.1.0__py3-none-any.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.
Files changed (47) hide show
  1. modulex/__init__.py +39 -0
  2. modulex/_base.py +281 -0
  3. modulex/_client.py +237 -0
  4. modulex/_compat.py +39 -0
  5. modulex/_config.py +26 -0
  6. modulex/_exceptions.py +131 -0
  7. modulex/_streaming.py +118 -0
  8. modulex/py.typed +0 -0
  9. modulex/resources/__init__.py +1 -0
  10. modulex/resources/api_keys.py +39 -0
  11. modulex/resources/auth.py +38 -0
  12. modulex/resources/chats.py +62 -0
  13. modulex/resources/composer.py +134 -0
  14. modulex/resources/credentials.py +197 -0
  15. modulex/resources/dashboard.py +110 -0
  16. modulex/resources/deployments.py +92 -0
  17. modulex/resources/executions.py +97 -0
  18. modulex/resources/integrations.py +110 -0
  19. modulex/resources/knowledge.py +343 -0
  20. modulex/resources/notifications.py +39 -0
  21. modulex/resources/organizations.py +72 -0
  22. modulex/resources/schedules.py +172 -0
  23. modulex/resources/subscriptions.py +38 -0
  24. modulex/resources/system.py +28 -0
  25. modulex/resources/templates.py +115 -0
  26. modulex/resources/workflows.py +156 -0
  27. modulex/types/__init__.py +294 -0
  28. modulex/types/api_keys.py +19 -0
  29. modulex/types/auth.py +62 -0
  30. modulex/types/chats.py +55 -0
  31. modulex/types/composer.py +27 -0
  32. modulex/types/credentials.py +79 -0
  33. modulex/types/dashboard.py +54 -0
  34. modulex/types/executions.py +104 -0
  35. modulex/types/integrations.py +29 -0
  36. modulex/types/knowledge.py +75 -0
  37. modulex/types/notifications.py +16 -0
  38. modulex/types/organizations.py +43 -0
  39. modulex/types/schedules.py +48 -0
  40. modulex/types/shared.py +39 -0
  41. modulex/types/subscriptions.py +59 -0
  42. modulex/types/templates.py +50 -0
  43. modulex/types/workflows.py +253 -0
  44. modulex_python-0.1.0.dist-info/METADATA +435 -0
  45. modulex_python-0.1.0.dist-info/RECORD +47 -0
  46. modulex_python-0.1.0.dist-info/WHEEL +4 -0
  47. modulex_python-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,197 @@
1
+ """Credentials resource for the ModuleX Python SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from modulex._base import _BaseResource
8
+ from modulex._streaming import EventSourceStream
9
+
10
+
11
+ class Credentials(_BaseResource):
12
+ """Resource for managing integration credentials."""
13
+
14
+ async def list(
15
+ self,
16
+ *,
17
+ integration_name: str | None = None,
18
+ auth_type: str | None = None,
19
+ limit: int = 100,
20
+ offset: int = 0,
21
+ organization_id: str | None = None,
22
+ ) -> Any:
23
+ """Return a paginated list of credentials, optionally filtered by integration or auth type."""
24
+ params: dict[str, Any] = {"limit": limit, "offset": offset}
25
+ if integration_name is not None:
26
+ params["integration_name"] = integration_name
27
+ if auth_type is not None:
28
+ params["auth_type"] = auth_type
29
+ return await self._get("/credentials", params=params, organization_id=organization_id)
30
+
31
+ async def get(
32
+ self,
33
+ credential_id: str,
34
+ *,
35
+ include_masked: bool = False,
36
+ organization_id: str | None = None,
37
+ ) -> Any:
38
+ """Return a single credential by its ID, optionally including masked auth data."""
39
+ return await self._get(
40
+ f"/credentials/{credential_id}",
41
+ params={"include_masked": include_masked},
42
+ organization_id=organization_id,
43
+ )
44
+
45
+ async def create(
46
+ self,
47
+ integration_name: str,
48
+ *,
49
+ auth_data: dict[str, Any] | None = None,
50
+ auth_type: str | None = None,
51
+ display_name: str | None = None,
52
+ metadata: dict[str, Any] | None = None,
53
+ make_default: bool = False,
54
+ expires_at: str | None = None,
55
+ organization_id: str | None = None,
56
+ ) -> Any:
57
+ """Create a new credential for the specified integration."""
58
+ body: dict[str, Any] = {
59
+ "integration_name": integration_name,
60
+ "make_default": make_default,
61
+ }
62
+ if auth_data is not None:
63
+ body["auth_data"] = auth_data
64
+ if auth_type is not None:
65
+ body["auth_type"] = auth_type
66
+ if display_name is not None:
67
+ body["display_name"] = display_name
68
+ if metadata is not None:
69
+ body["metadata"] = metadata
70
+ if expires_at is not None:
71
+ body["expires_at"] = expires_at
72
+ return await self._post("/credentials", json=body, organization_id=organization_id)
73
+
74
+ async def update(
75
+ self,
76
+ credential_id: str,
77
+ *,
78
+ display_name: str | None = None,
79
+ metadata: dict[str, Any] | None = None,
80
+ organization_id: str | None = None,
81
+ ) -> Any:
82
+ """Update the display name or metadata of an existing credential."""
83
+ body: dict[str, Any] = {}
84
+ if display_name is not None:
85
+ body["display_name"] = display_name
86
+ if metadata is not None:
87
+ body["metadata"] = metadata
88
+ return await self._put(
89
+ f"/credentials/{credential_id}",
90
+ json=body or None,
91
+ organization_id=organization_id,
92
+ )
93
+
94
+ async def delete(self, credential_id: str, *, organization_id: str | None = None) -> None:
95
+ """Permanently delete a credential by its ID."""
96
+ await self._delete(f"/credentials/{credential_id}", organization_id=organization_id)
97
+
98
+ async def set_default(self, credential_id: str, *, organization_id: str | None = None) -> Any:
99
+ """Mark a credential as the default for its integration."""
100
+ return await self._post(
101
+ f"/credentials/{credential_id}/set-default",
102
+ organization_id=organization_id,
103
+ )
104
+
105
+ async def test(self, credential_id: str, *, organization_id: str | None = None) -> Any:
106
+ """Test an existing credential to verify it is still valid."""
107
+ return await self._post(f"/credentials/{credential_id}/test", organization_id=organization_id)
108
+
109
+ async def test_temporary(
110
+ self,
111
+ integration_name: str,
112
+ auth_type: str,
113
+ auth_data: dict[str, Any],
114
+ *,
115
+ organization_id: str | None = None,
116
+ ) -> Any:
117
+ """Test a set of credentials without persisting them."""
118
+ body: dict[str, Any] = {
119
+ "integration_name": integration_name,
120
+ "auth_type": auth_type,
121
+ "auth_data": auth_data,
122
+ }
123
+ return await self._post("/credentials/test-temporary", json=body, organization_id=organization_id)
124
+
125
+ async def usage(
126
+ self,
127
+ credential_id: str,
128
+ *,
129
+ start_date: str | None = None,
130
+ end_date: str | None = None,
131
+ organization_id: str | None = None,
132
+ ) -> Any:
133
+ """Return usage statistics for a credential within an optional date range."""
134
+ params: dict[str, Any] = {}
135
+ if start_date is not None:
136
+ params["start_date"] = start_date
137
+ if end_date is not None:
138
+ params["end_date"] = end_date
139
+ return await self._get(
140
+ f"/credentials/{credential_id}/usage",
141
+ params=params or None,
142
+ organization_id=organization_id,
143
+ )
144
+
145
+ async def audit(
146
+ self,
147
+ credential_id: str,
148
+ *,
149
+ limit: int = 100,
150
+ offset: int = 0,
151
+ organization_id: str | None = None,
152
+ ) -> Any:
153
+ """Return the audit log for a credential."""
154
+ return await self._get(
155
+ f"/credentials/{credential_id}/audit",
156
+ params={"limit": limit, "offset": offset},
157
+ organization_id=organization_id,
158
+ )
159
+
160
+ async def create_mcp_server(
161
+ self,
162
+ server_url: str,
163
+ *,
164
+ headers: dict[str, Any] | None = None,
165
+ display_name: str | None = None,
166
+ make_default: bool = False,
167
+ organization_id: str | None = None,
168
+ ) -> Any:
169
+ """Register an MCP server as a credential."""
170
+ body: dict[str, Any] = {"server_url": server_url, "make_default": make_default}
171
+ if headers is not None:
172
+ body["headers"] = headers
173
+ if display_name is not None:
174
+ body["display_name"] = display_name
175
+ return await self._post("/credentials/mcp-server", json=body, organization_id=organization_id)
176
+
177
+ async def refresh_mcp_discovery(self, credential_id: str, *, organization_id: str | None = None) -> Any:
178
+ """Refresh the tool discovery for an MCP server credential."""
179
+ return await self._post(
180
+ f"/credentials/{credential_id}/refresh-discovery",
181
+ organization_id=organization_id,
182
+ )
183
+
184
+ async def mcp_tools(self, credential_id: str, *, organization_id: str | None = None) -> Any:
185
+ """Return the list of tools exposed by an MCP server credential."""
186
+ return await self._get(
187
+ f"/credentials/{credential_id}/mcp-tools",
188
+ organization_id=organization_id,
189
+ )
190
+
191
+ def bulk_modulex_keys_stream(self, *, organization_id: str | None = None) -> EventSourceStream:
192
+ """Open an SSE stream to receive bulk ModuleX key data."""
193
+ return self._stream_sse(
194
+ "/credentials/bulk-modulex-keys/stream",
195
+ method="POST",
196
+ organization_id=organization_id,
197
+ )
@@ -0,0 +1,110 @@
1
+ """Dashboard resource for the ModuleX Python SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from modulex._base import _BaseResource
8
+
9
+
10
+ class Dashboard(_BaseResource):
11
+ """Resource for accessing dashboard logs, analytics, and user management."""
12
+
13
+ async def logs(
14
+ self,
15
+ *,
16
+ limit: int = 50,
17
+ offset: int = 0,
18
+ category: str | None = None,
19
+ operation: str | None = None,
20
+ start_date: str | None = None,
21
+ end_date: str | None = None,
22
+ organization_id: str | None = None,
23
+ ) -> Any:
24
+ """Return activity logs for the organization with optional filters."""
25
+ params: dict[str, Any] = {
26
+ k: v
27
+ for k, v in {
28
+ "limit": limit,
29
+ "offset": offset,
30
+ "category": category,
31
+ "operation": operation,
32
+ "start_date": start_date,
33
+ "end_date": end_date,
34
+ }.items()
35
+ if v is not None
36
+ }
37
+ return await self._get("/dashboard/logs", params=params, organization_id=organization_id)
38
+
39
+ async def analytics_overview(
40
+ self,
41
+ *,
42
+ limit: int = 20,
43
+ offset: int = 0,
44
+ organization_id: str | None = None,
45
+ ) -> Any:
46
+ """Return a high-level analytics overview for the organization."""
47
+ params: dict[str, Any] = {"limit": limit, "offset": offset}
48
+ return await self._get(
49
+ "/dashboard/analytics/overview",
50
+ params=params,
51
+ organization_id=organization_id,
52
+ )
53
+
54
+ async def analytics_tools(
55
+ self,
56
+ *,
57
+ period: str = "7d",
58
+ limit: int = 20,
59
+ offset: int = 0,
60
+ organization_id: str | None = None,
61
+ ) -> Any:
62
+ """Return tool usage analytics for the organization over a given period."""
63
+ params: dict[str, Any] = {"period": period, "limit": limit, "offset": offset}
64
+ return await self._get(
65
+ "/dashboard/analytics/tools",
66
+ params=params,
67
+ organization_id=organization_id,
68
+ )
69
+
70
+ async def analytics_llm_usage(
71
+ self,
72
+ *,
73
+ period: str = "7d",
74
+ limit: int = 20,
75
+ offset: int = 0,
76
+ organization_id: str | None = None,
77
+ ) -> Any:
78
+ """Return LLM token consumption analytics for the organization over a given period."""
79
+ params: dict[str, Any] = {"period": period, "limit": limit, "offset": offset}
80
+ return await self._get(
81
+ "/dashboard/analytics/llm-usage",
82
+ params=params,
83
+ organization_id=organization_id,
84
+ )
85
+
86
+ async def users(
87
+ self,
88
+ *,
89
+ search: str | None = None,
90
+ status: str | None = None,
91
+ sort_by: str = "created_at",
92
+ order: str = "desc",
93
+ page: int = 1,
94
+ limit: int = 10,
95
+ organization_id: str | None = None,
96
+ ) -> Any:
97
+ """Return paginated users for the organization with optional search and sort."""
98
+ params: dict[str, Any] = {
99
+ k: v
100
+ for k, v in {
101
+ "search": search,
102
+ "status": status,
103
+ "sort_by": sort_by,
104
+ "order": order,
105
+ "page": page,
106
+ "limit": limit,
107
+ }.items()
108
+ if v is not None
109
+ }
110
+ return await self._get("/dashboard/users", params=params, organization_id=organization_id)
@@ -0,0 +1,92 @@
1
+ """Deployments resource for the ModuleX Python SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from modulex._base import _BaseResource
8
+
9
+
10
+ class Deployments(_BaseResource):
11
+ """Resource for managing workflow deployments."""
12
+
13
+ async def create(
14
+ self,
15
+ workflow_id: str,
16
+ *,
17
+ deployment_note: str | None = None,
18
+ schema_image_url: str | None = None,
19
+ organization_id: str | None = None,
20
+ ) -> Any:
21
+ """Deploy a workflow and create a new deployment record."""
22
+ body: dict[str, Any] = {}
23
+ if deployment_note is not None:
24
+ body["deployment_note"] = deployment_note
25
+ if schema_image_url is not None:
26
+ body["schema_image_url"] = schema_image_url
27
+ return await self._post(
28
+ f"/workflows/{workflow_id}/deploy",
29
+ json=body or None,
30
+ organization_id=organization_id,
31
+ )
32
+
33
+ async def list(
34
+ self,
35
+ workflow_id: str,
36
+ *,
37
+ limit: int = 20,
38
+ offset: int = 0,
39
+ organization_id: str | None = None,
40
+ ) -> Any:
41
+ """Return a paginated list of deployments for a workflow."""
42
+ return await self._get(
43
+ f"/workflows/{workflow_id}/deployments",
44
+ params={"limit": limit, "offset": offset},
45
+ organization_id=organization_id,
46
+ )
47
+
48
+ async def get(
49
+ self,
50
+ workflow_id: str,
51
+ deployment_id: str,
52
+ *,
53
+ organization_id: str | None = None,
54
+ ) -> Any:
55
+ """Return a single deployment by workflow ID and deployment ID."""
56
+ return await self._get(
57
+ f"/workflows/{workflow_id}/deployments/{deployment_id}",
58
+ organization_id=organization_id,
59
+ )
60
+
61
+ async def activate(
62
+ self,
63
+ workflow_id: str,
64
+ deployment_id: str,
65
+ *,
66
+ organization_id: str | None = None,
67
+ ) -> Any:
68
+ """Activate a specific deployment, making it the live version."""
69
+ return await self._put(
70
+ f"/workflows/{workflow_id}/deployments/{deployment_id}/activate",
71
+ organization_id=organization_id,
72
+ )
73
+
74
+ async def deactivate(self, workflow_id: str, *, organization_id: str | None = None) -> Any:
75
+ """Deactivate the currently live deployment for a workflow."""
76
+ return await self._delete(
77
+ f"/workflows/{workflow_id}/deployments/live",
78
+ organization_id=organization_id,
79
+ )
80
+
81
+ async def delete(
82
+ self,
83
+ workflow_id: str,
84
+ deployment_id: str,
85
+ *,
86
+ organization_id: str | None = None,
87
+ ) -> Any:
88
+ """Permanently delete a deployment by workflow ID and deployment ID."""
89
+ return await self._delete(
90
+ f"/workflows/{workflow_id}/deployments/{deployment_id}",
91
+ organization_id=organization_id,
92
+ )
@@ -0,0 +1,97 @@
1
+ """Executions resource for the ModuleX Python SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from modulex._base import _BaseResource
8
+ from modulex._streaming import EventSourceStream
9
+
10
+
11
+ class Executions(_BaseResource):
12
+ """Resource for running and managing workflow executions."""
13
+
14
+ async def run(
15
+ self,
16
+ *,
17
+ workflow_id: str | None = None,
18
+ workflow: dict[str, Any] | None = None,
19
+ llm: dict[str, Any] | None = None,
20
+ system_workflow: str | None = None,
21
+ input: dict[str, Any] | None = None,
22
+ config: dict[str, Any] | None = None,
23
+ stream: bool = True,
24
+ ephemeral: bool = False,
25
+ is_private: bool = False,
26
+ knowledge_config: dict[str, Any] | None = None,
27
+ organization_id: str | None = None,
28
+ ) -> Any:
29
+ """Trigger a workflow execution and return the run result or stream handle."""
30
+ body: dict[str, Any] = {
31
+ "stream": stream,
32
+ "ephemeral": ephemeral,
33
+ "is_private": is_private,
34
+ }
35
+ if workflow_id is not None:
36
+ body["workflow_id"] = workflow_id
37
+ if workflow is not None:
38
+ body["workflow"] = workflow
39
+ if llm is not None:
40
+ body["llm"] = llm
41
+ if system_workflow is not None:
42
+ body["system_workflow"] = system_workflow
43
+ if input is not None:
44
+ body["input"] = input
45
+ if config is not None:
46
+ body["config"] = config
47
+ if knowledge_config is not None:
48
+ body["knowledge_config"] = knowledge_config
49
+ return await self._post("/workflows/run", json=body, organization_id=organization_id)
50
+
51
+ async def get_state(self, thread_id: str, *, organization_id: str | None = None) -> Any:
52
+ """Return the current state of a workflow thread."""
53
+ return await self._get(f"/workflows/state/{thread_id}", organization_id=organization_id)
54
+
55
+ async def resume(
56
+ self,
57
+ thread_id: str,
58
+ run_id: str,
59
+ resume_value: Any,
60
+ *,
61
+ workflow_id: str | None = None,
62
+ workflow: dict[str, Any] | None = None,
63
+ stream: bool = True,
64
+ organization_id: str | None = None,
65
+ ) -> Any:
66
+ """Resume a paused workflow thread with the provided resume value."""
67
+ body: dict[str, Any] = {
68
+ "run_id": run_id,
69
+ "resume_value": resume_value,
70
+ "stream": stream,
71
+ }
72
+ if workflow_id is not None:
73
+ body["workflow_id"] = workflow_id
74
+ if workflow is not None:
75
+ body["workflow"] = workflow
76
+ return await self._post(f"/workflows/resume/{thread_id}", json=body, organization_id=organization_id)
77
+
78
+ async def cancel(
79
+ self,
80
+ run_id: str,
81
+ *,
82
+ reason: str | None = None,
83
+ organization_id: str | None = None,
84
+ ) -> Any:
85
+ """Cancel an in-progress workflow run by its run ID."""
86
+ body: dict[str, Any] = {}
87
+ if reason is not None:
88
+ body["reason"] = reason
89
+ return await self._post(
90
+ f"/workflows/cancel/{run_id}",
91
+ json=body or None,
92
+ organization_id=organization_id,
93
+ )
94
+
95
+ def listen(self, run_id: str, *, organization_id: str | None = None) -> EventSourceStream:
96
+ """Open an SSE stream to receive live events for a workflow run."""
97
+ return self._stream_sse(f"/workflows/listen/{run_id}", organization_id=organization_id)
@@ -0,0 +1,110 @@
1
+ """Integrations resource for the ModuleX Python SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from modulex._base import _BaseResource
8
+
9
+
10
+ class Integrations(_BaseResource):
11
+ """Resource for browsing and inspecting available integrations."""
12
+
13
+ async def browse(
14
+ self,
15
+ *,
16
+ category: str | None = None,
17
+ type: str | None = None,
18
+ auth_type: str | None = None,
19
+ search: str | None = None,
20
+ include_details: bool = True,
21
+ paginate: bool = True,
22
+ page: int = 1,
23
+ page_size: int = 50,
24
+ organization_id: str | None = None,
25
+ ) -> Any:
26
+ """Browse all available integrations with optional filters and pagination."""
27
+ params: dict[str, Any] = {
28
+ k: v
29
+ for k, v in {
30
+ "category": category,
31
+ "type": type,
32
+ "auth_type": auth_type,
33
+ "search": search,
34
+ "include_details": include_details,
35
+ "paginate": paginate,
36
+ "page": page,
37
+ "page_size": page_size,
38
+ }.items()
39
+ if v is not None
40
+ }
41
+ return await self._get("/integrations/browse", params=params, organization_id=organization_id)
42
+
43
+ async def tools(
44
+ self,
45
+ *,
46
+ category: str | None = None,
47
+ organization_id: str | None = None,
48
+ ) -> Any:
49
+ """Return available tool integrations, optionally filtered by category."""
50
+ params: dict[str, Any] = {k: v for k, v in {"category": category}.items() if v is not None}
51
+ return await self._get("/integrations/tools", params=params, organization_id=organization_id)
52
+
53
+ async def tool_detail(
54
+ self,
55
+ integration_name: str,
56
+ *,
57
+ organization_id: str | None = None,
58
+ ) -> Any:
59
+ """Return detailed information for a single tool integration by name."""
60
+ return await self._get(f"/integrations/tools/{integration_name}", organization_id=organization_id)
61
+
62
+ async def llm_providers(
63
+ self,
64
+ *,
65
+ category: str | None = None,
66
+ organization_id: str | None = None,
67
+ ) -> Any:
68
+ """Return available LLM provider integrations, optionally filtered by category."""
69
+ params: dict[str, Any] = {k: v for k, v in {"category": category}.items() if v is not None}
70
+ return await self._get("/integrations/llm-providers", params=params, organization_id=organization_id)
71
+
72
+ async def llm_provider_detail(
73
+ self,
74
+ provider_name: str,
75
+ *,
76
+ organization_id: str | None = None,
77
+ ) -> Any:
78
+ """Return detailed information for a single LLM provider by name."""
79
+ return await self._get(f"/integrations/llm-providers/{provider_name}", organization_id=organization_id)
80
+
81
+ async def knowledge_providers(
82
+ self,
83
+ *,
84
+ category: str | None = None,
85
+ organization_id: str | None = None,
86
+ ) -> Any:
87
+ """Return available knowledge provider integrations, optionally filtered by category."""
88
+ params: dict[str, Any] = {k: v for k, v in {"category": category}.items() if v is not None}
89
+ return await self._get("/integrations/knowledge-providers", params=params, organization_id=organization_id)
90
+
91
+ async def knowledge_provider_detail(
92
+ self,
93
+ provider_name: str,
94
+ *,
95
+ organization_id: str | None = None,
96
+ ) -> Any:
97
+ """Return detailed information for a single knowledge provider by name."""
98
+ return await self._get(
99
+ f"/integrations/knowledge-providers/{provider_name}",
100
+ organization_id=organization_id,
101
+ )
102
+
103
+ async def get(
104
+ self,
105
+ integration_name: str,
106
+ *,
107
+ organization_id: str | None = None,
108
+ ) -> Any:
109
+ """Return the integration record for a given integration name."""
110
+ return await self._get(f"/integrations/{integration_name}", organization_id=organization_id)