clappia-api-tools 0.1.5__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.
@@ -0,0 +1,217 @@
1
+ Metadata-Version: 2.4
2
+ Name: clappia-api-tools
3
+ Version: 0.1.5
4
+ Summary: Python client for Clappia API integration
5
+ Author-email: Rishabh Verma <rishabh.v@clappia.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/clappia-dev/clappia-tools
8
+ Project-URL: Documentation, https://github.com/clappia-dev/clappia-tools#readme
9
+ Project-URL: Repository, https://github.com/clappia-dev/clappia-tools.git
10
+ Project-URL: Bug Tracker, https://github.com/clappia-dev/clappia-tools/issues
11
+ Keywords: clappia,client,mcp,rest-apis,tools,workchat,agents
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: requests>=2.31.0
24
+ Requires-Dist: python-dotenv>=1.0.0
25
+ Requires-Dist: pydantic>=2.0.0
26
+ Requires-Dist: typing-extensions>=4.8.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
29
+ Requires-Dist: black>=23.0.0; extra == "dev"
30
+ Requires-Dist: flake8>=6.0.0; extra == "dev"
31
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
32
+ Requires-Dist: pre-commit>=3.0.0; extra == "dev"
33
+ Provides-Extra: test
34
+ Requires-Dist: pytest>=7.0.0; extra == "test"
35
+ Requires-Dist: pytest-cov>=4.0.0; extra == "test"
36
+ Requires-Dist: pytest-mock>=3.10.0; extra == "test"
37
+ Provides-Extra: docs
38
+ Requires-Dist: mkdocs>=1.4.0; extra == "docs"
39
+ Requires-Dist: mkdocs-material>=9.0.0; extra == "docs"
40
+ Dynamic: license-file
41
+
42
+ # Clappia Tools
43
+
44
+ **LangChain integration for Clappia API**
45
+
46
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/clappia-tools)](https://pypi.org/project/clappia-tools/)
47
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
48
+
49
+ ---
50
+
51
+ ## Overview
52
+
53
+ Clappia Tools is a Python package that provides a unified client and a set of tools for seamless integration with the [Clappia API](https://www.clappia.com/). It enables developers to automate workflows, manage submissions, and interact with Clappia apps programmatically. The package is designed for use in automation, data integration, and agent-based systems (e.g., LangChain agents).
54
+
55
+ ---
56
+
57
+ ## Features
58
+
59
+ - **Unified API Client**: One client for all Clappia API operations.
60
+ - **Submission Management**: Create, edit, update owners, and change status of submissions.
61
+ - **App Definition Retrieval**: Fetch complete app structure and metadata.
62
+ - **Input Validation**: Built-in validation for IDs, emails, and status objects.
63
+ - **Extensible Tools**: Modular functions for each operation, easily integrated into agents or scripts.
64
+ - **Comprehensive Testing**: Includes unit and integration tests.
65
+
66
+ ---
67
+
68
+ ## Installation
69
+
70
+ ```bash
71
+ pip install clappia-tools
72
+ ```
73
+
74
+ Or, for development:
75
+
76
+ ```bash
77
+ git clone https://github.com/clappia-dev/clappia-tools.git
78
+ cd clappia-tools
79
+ pip install -e .[dev]
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Configuration
85
+
86
+ You must provide your Clappia API credentials and workspace information directly when initializing the `ClappiaClient`:
87
+
88
+ - `api_key`: Your Clappia API key
89
+ - `base_url`: The base URL for the Clappia API (e.g., `https://api.clappia.com`)
90
+ - `workplace_id`: Your Clappia workplace ID
91
+
92
+ **Example:**
93
+
94
+ ```python
95
+ from clappia_tools import ClappiaClient
96
+
97
+ client = ClappiaClient(
98
+ api_key="your-api-key",
99
+ base_url="https://api.clappia.com",
100
+ workplace_id="your-workplace-id"
101
+ )
102
+ ```
103
+ ---
104
+
105
+ ## Usage
106
+
107
+ ### Basic Client Usage
108
+
109
+ ```python
110
+ from clappia_tools import ClappiaClient
111
+
112
+ client = ClappiaClient(
113
+ api_key="your-api-key",
114
+ base_url="https://api.clappia.com",
115
+ workplace_id="your-workplace-id"
116
+ )
117
+
118
+ # Create a submission
119
+ result = client.create_submission(
120
+ app_id="MFX093412",
121
+ data={"employee_name": "John Doe", "department": "Engineering"},
122
+ email="user@example.com"
123
+ )
124
+ print(result)
125
+
126
+ # Edit a submission
127
+ result = client.edit_submission(
128
+ app_id="MFX093412",
129
+ submission_id="HGO51464561",
130
+ data={"department": "Marketing"},
131
+ email="user@example.com"
132
+ )
133
+ print(result)
134
+
135
+ # Get app definition
136
+ result = client.get_app_definition(app_id="MFX093412")
137
+ print(result)
138
+ ```
139
+
140
+ ### Tool Functions
141
+
142
+ You can also use the modular tool functions directly:
143
+
144
+ ```python
145
+ from clappia_tools._tools import (
146
+ create_clappia_submission,
147
+ edit_clappia_submission,
148
+ get_app_definition,
149
+ update_clappia_submission_owners,
150
+ update_clappia_submission_status,
151
+ )
152
+
153
+ # Create a submission
154
+ data = {"employee_name": "Jane Doe", "department": "HR"}
155
+ response = create_clappia_submission("MFX093412", data, "user@example.com")
156
+ print(response)
157
+
158
+ # Update submission owners
159
+ response = update_clappia_submission_owners(
160
+ "MFX093412", "HGO51464561", "admin@example.com", ["user1@company.com", "user2@company.com"]
161
+ )
162
+ print(response)
163
+
164
+ # Update submission status
165
+ response = update_clappia_submission_status(
166
+ "MFX093412", "HGO51464561", "admin@example.com", {"statusName": "Approved", "comments": "Reviewed."}
167
+ )
168
+ print(response)
169
+ ```
170
+
171
+ ---
172
+
173
+ ## Available Tools
174
+
175
+ - `create_clappia_submission(app_id, data, email)`
176
+ - `edit_clappia_submission(app_id, submission_id, data, email)`
177
+ - `get_app_definition(app_id, language="en", strip_html=True, include_tags=True)`
178
+ - `update_clappia_submission_owners(app_id, submission_id, requesting_user_email_address, email_ids)`
179
+ - `update_clappia_submission_status(app_id, submission_id, requesting_user_email_address, status)`
180
+
181
+ See docstrings in each tool for detailed argument and return value descriptions.
182
+
183
+ ---
184
+
185
+ ## Input Validation
186
+
187
+ - **App ID**: Must be uppercase letters and numbers (e.g., `MFX093412`).
188
+ - **Submission ID**: Must be uppercase letters and numbers (e.g., `HGO51464561`).
189
+ - **Email**: Must be a valid email address.
190
+ - **Status**: Must be a dictionary with a non-empty `statusName` or `name` field.
191
+
192
+ Invalid inputs will return descriptive error messages.
193
+
194
+ ---
195
+
196
+ ## Testing
197
+
198
+ Run all tests (unit and integration):
199
+
200
+ ```bash
201
+ pytest
202
+ ```
203
+
204
+ ---
205
+
206
+ ## Contributing
207
+
208
+ 1. Fork the repository and create your branch.
209
+ 2. Write clear, well-documented code and tests.
210
+ 3. Run `pytest` and ensure all tests pass.
211
+ 4. Submit a pull request with a clear description of your changes.
212
+
213
+ ---
214
+
215
+ ## License
216
+
217
+ This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.
@@ -0,0 +1,27 @@
1
+ clappia_api_tools-0.1.5.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ clappia_tools/__init__.py,sha256=p5I5RBY9Y6POnuOf4yKbea_yQ0CD7fTUz4JicjbwvBA,519
3
+ clappia_tools/_enums/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ clappia_tools/_enums/enums.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ clappia_tools/_models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ clappia_tools/_models/model.py,sha256=mIXynyUeIK9KrAcDNKRkAFQExr7lZTAm4Rs_LPveZSU,897
7
+ clappia_tools/_utils/__init__.py,sha256=X7W407Kjb4fCc7OcWqex6X_P893k4qLKi-pdgd-6z60,136
8
+ clappia_tools/_utils/api_utils.py,sha256=_J6e1rdAcbndQeQ9i0QsqpdkwHYR5oKdvdAbBcT0OCg,4711
9
+ clappia_tools/_utils/logging_utils.py,sha256=OAZTKJKBPSXFMMViSXVji2FwJ4mY_jGiMZJ9DAdJY8s,3097
10
+ clappia_tools/_utils/validators.py,sha256=W5yIJnr241ZzPg3LyJerKZs8W0Lj7OzLliKbKXayZqE,4866
11
+ clappia_tools/client/__init__.py,sha256=XQbzWq6BDOWs_7iPHBEYBbh55lhzg1Yw4f_xsIbPWFQ,359
12
+ clappia_tools/client/app_definition_client.py,sha256=ITY_hKD_9yuWvk7Ol-oE2GMtLYDUqHzvWudqGcBq3eg,3789
13
+ clappia_tools/client/app_management_client.py,sha256=7ij7lFKCr5NQ5tFJi65n1zHAEhZwxsxEMGXlL2zyz_4,23835
14
+ clappia_tools/client/base_client.py,sha256=0_R_cFPSCqjNPcG0gHPgBwviUOxlkfSIwVQlgiCi2so,833
15
+ clappia_tools/client/clappia_client.py,sha256=h2zSqhSFHB4Ijh-RaxiRg1CXfyxXWEgNwqj92FJ1N5k,10007
16
+ clappia_tools/client/submission_client.py,sha256=jviQdE40qbqFEhWNX-ToHAvMoy-nqjAGkJQef4D3ihY,14494
17
+ clappia_tools/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ clappia_tools/tests/integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ clappia_tools/tests/integration/definition/test_tools_definition.py,sha256=jrzPx8VNPkpqJcfS-NVGAcsEix_u8tWtnwAk_dflJ2A,16572
20
+ clappia_tools/tests/integration/submission/test_tools_submission.py,sha256=vFLQ7T-poPjhvmUvGctsVygqEMbsAyZfpuLroBbTEL0,9796
21
+ clappia_tools/tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ clappia_tools/tests/unit/definition/test_validators.py,sha256=d2QdIoL8mKWN0GSIYW5_4SQhNP-mNAeEmQc5rVpOWiE,7821
23
+ clappia_tools/tests/unit/submission/test_client.py,sha256=uEiz1D0qdS-5C86FMzd1VR_Emp7Cedz6CVbIiMJiuZw,10771
24
+ clappia_api_tools-0.1.5.dist-info/METADATA,sha256=m6rGHe5uvy1k1K9pDH-85PDn6lGDhRbqgoVCJWKBF3Y,6427
25
+ clappia_api_tools-0.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
26
+ clappia_api_tools-0.1.5.dist-info/top_level.txt,sha256=q8xOO-1KTRJwcux9LiCzVLbYG6KrlF1uH6bsQFx3kpo,14
27
+ clappia_api_tools-0.1.5.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
File without changes
@@ -0,0 +1 @@
1
+ clappia_tools
@@ -0,0 +1,17 @@
1
+ """
2
+ Clappia Tools - LangChain integration for Clappia API
3
+
4
+ This package provides a unified client for interacting with Clappia APIs.
5
+ """
6
+
7
+ from .client.clappia_client import ClappiaClient
8
+ from .client.app_definition_client import AppDefinitionClient
9
+ from .client.app_management_client import AppManagementClient
10
+ from .client.submission_client import SubmissionClient
11
+
12
+ __version__ = "0.1.5"
13
+ __all__ = ["ClappiaClient", "AppDefinitionClient", "AppManagementClient", "SubmissionClient"]
14
+
15
+
16
+ def __dir__():
17
+ return __all__
File without changes
File without changes
File without changes
@@ -0,0 +1,40 @@
1
+ import os
2
+ from dataclasses import dataclass
3
+ from typing import List, Optional
4
+
5
+ @dataclass
6
+ class Field:
7
+ """Field model for creating an app"""
8
+ """
9
+ fieldType: str
10
+ label: str
11
+ options: Optional[List[str]] = None
12
+ """
13
+
14
+ fieldType: str
15
+ label: str
16
+ options: Optional[List[str]] = None
17
+ def to_dict(self) -> dict:
18
+ result = {
19
+ "fieldType": self.fieldType,
20
+ "label": self.label,
21
+ }
22
+ if self.options is not None:
23
+ result["options"] = self.options
24
+ return result
25
+
26
+ @dataclass
27
+ class Section:
28
+ """Section model for creating an app"""
29
+ """
30
+ sectionName: str
31
+ fields: List[Field]
32
+ """
33
+
34
+ sectionName: str
35
+ fields: List[Field]
36
+ def to_dict(self) -> dict:
37
+ return {
38
+ "sectionName": self.sectionName,
39
+ "fields": [field.to_dict() for field in self.fields]
40
+ }
@@ -0,0 +1,5 @@
1
+ __all__ = []
2
+
3
+ from .logging_utils import get_logger
4
+ from .validators import ClappiaInputValidator
5
+ from .api_utils import ClappiaAPIUtils
@@ -0,0 +1,141 @@
1
+ import os
2
+ import json
3
+ import requests
4
+ from typing import Optional, Dict, Any, Tuple
5
+ from clappia_tools._utils.logging_utils import get_logger
6
+
7
+ logger = get_logger(__name__)
8
+
9
+
10
+ class ClappiaAPIUtils:
11
+ """Utilities for Clappia API interactions"""
12
+
13
+ def __init__(
14
+ self,
15
+ api_key: str,
16
+ base_url: str,
17
+ workplace_id: str,
18
+ timeout: int = 30,
19
+ ):
20
+ """
21
+ Initialize API utilities with configurable parameters
22
+
23
+ Args:
24
+ api_key: Clappia API key
25
+ base_url: API base URL
26
+ workplace_id: Workplace ID
27
+ timeout: Request timeout in seconds
28
+ """
29
+ self.api_key = api_key
30
+ self.base_url = base_url
31
+ self.workplace_id = workplace_id
32
+ self.timeout = timeout
33
+
34
+ def validate_environment(self) -> Tuple[bool, str]:
35
+ """Validate that required configuration is available"""
36
+ if not self.api_key:
37
+ return (
38
+ False,
39
+ "API key is not configured",
40
+ )
41
+ if not self.base_url:
42
+ return (
43
+ False,
44
+ "Base URL is not configured",
45
+ )
46
+ if not self.workplace_id:
47
+ return (
48
+ False,
49
+ "Workplace ID is not configured",
50
+ )
51
+ return True, ""
52
+
53
+ def get_headers(self) -> Dict[str, str]:
54
+ """Get standard headers for API requests"""
55
+ return {
56
+ "x-api-key": self.api_key,
57
+ "Content-Type": "application/json",
58
+ "workplaceId": self.workplace_id,
59
+ }
60
+
61
+ def handle_response(
62
+ self, response: requests.Response
63
+ ) -> Tuple[bool, Optional[str], Optional[Dict[str, Any]]]:
64
+ """
65
+ Handle API response and return structured result
66
+
67
+ Returns:
68
+ Tuple of (success: bool, error_message: str, data: dict)
69
+ """
70
+ if response.status_code == 200:
71
+ try:
72
+ return True, None, response.json()
73
+ except json.JSONDecodeError:
74
+ logger.warning(f"Valid response but invalid JSON: {response.text}")
75
+ return True, None, {"raw_response": response.text}
76
+
77
+ error_message = self._format_error_message(response)
78
+ return False, error_message, None
79
+
80
+ def _format_error_message(self, response: requests.Response) -> str:
81
+ """Format error message from API response"""
82
+ if response.status_code in [400, 401, 403, 404]:
83
+ try:
84
+ error_data = response.json()
85
+ return f"API Error ({response.status_code}): {json.dumps(error_data, indent=2)}"
86
+ except json.JSONDecodeError:
87
+ return f"API Error ({response.status_code}): {response.text}"
88
+ else:
89
+ return f"Unexpected API response ({response.status_code}): {response.text}"
90
+
91
+ def make_request(
92
+ self,
93
+ method: str,
94
+ endpoint: str,
95
+ data: Optional[Dict[str, Any]] = None,
96
+ params: Optional[Dict[str, Any]] = None,
97
+ ) -> Tuple[bool, Optional[str], Optional[Dict[str, Any]]]:
98
+ """
99
+ Make HTTP request to Clappia API
100
+
101
+ Args:
102
+ method: HTTP method (GET, POST, etc.)
103
+ endpoint: API endpoint (will be appended to base_url)
104
+ data: Request body data (for POST/PUT requests)
105
+ params: Query parameters (for GET requests)
106
+
107
+ Returns:
108
+ Tuple of (success: bool, error_message: str, response_data: dict)
109
+ """
110
+ env_valid, env_error = self.validate_environment()
111
+ if not env_valid:
112
+ return False, f"Configuration error: {env_error}", None
113
+
114
+ url = f"{self.base_url.rstrip('/')}/{endpoint.lstrip('/')}"
115
+ headers = self.get_headers()
116
+
117
+ try:
118
+ logger.info(f"Making {method} request to {url}")
119
+ if data:
120
+ logger.debug(f"Request data: {json.dumps(data, indent=2)}")
121
+
122
+ response = requests.request(
123
+ method=method,
124
+ url=url,
125
+ headers=headers,
126
+ json=data,
127
+ params=params,
128
+ timeout=self.timeout,
129
+ )
130
+
131
+ logger.info(f"Response status: {response.status_code}")
132
+ logger.debug(f"Response body: {response.text}")
133
+
134
+ return self.handle_response(response)
135
+
136
+ except requests.exceptions.Timeout:
137
+ return False, f"Request timeout after {self.timeout} seconds", None
138
+ except requests.exceptions.ConnectionError:
139
+ return False, "Connection error - unable to reach Clappia API", None
140
+ except Exception as e:
141
+ return False, f"Unexpected error: {str(e)}", None
@@ -0,0 +1,132 @@
1
+ import sys
2
+ from datetime import datetime
3
+ from enum import Enum
4
+
5
+
6
+ class bcolors:
7
+ HEADER = "\033[95m"
8
+ OKBLUE = "\033[94m"
9
+ OKCYAN = "\033[96m"
10
+ OKGREEN = "\033[92m"
11
+ WARNING = "\033[93m"
12
+ FAIL = "\033[91m"
13
+ ENDC = "\033[0m"
14
+ BOLD = "\033[1m"
15
+ UNDERLINE = "\033[4m"
16
+
17
+
18
+ class LogLevel(Enum):
19
+ DEBUG = 0
20
+ INFO = 1
21
+ WARNING = 2
22
+ ERROR = 3
23
+ CRITICAL = 4
24
+
25
+
26
+ class Logger:
27
+ def __init__(
28
+ self,
29
+ name: str = "Logger",
30
+ level: LogLevel = LogLevel.INFO,
31
+ timestamp_format: str = "%Y-%m-%d %H:%M:%S",
32
+ ):
33
+ self.name = name
34
+ self.level = level
35
+ self.timestamp_format = timestamp_format
36
+
37
+ self.colors = {
38
+ LogLevel.DEBUG: bcolors.OKBLUE,
39
+ LogLevel.INFO: bcolors.OKGREEN,
40
+ LogLevel.WARNING: bcolors.WARNING,
41
+ LogLevel.ERROR: bcolors.FAIL,
42
+ LogLevel.CRITICAL: bcolors.HEADER,
43
+ }
44
+ self.reset_color = bcolors.ENDC
45
+
46
+ def _should_log(self, level: LogLevel) -> bool:
47
+ return level.value >= self.level.value
48
+
49
+ def _format_message(self, level: LogLevel, message: str) -> str:
50
+ timestamp = datetime.now().strftime(self.timestamp_format)
51
+ return f"[{timestamp}] [{self.name}] [{level.name}] {message}"
52
+
53
+ def _log(self, level: LogLevel, message: str):
54
+ if not self._should_log(level):
55
+ return
56
+
57
+ message_str = str(message)
58
+ formatted_message = self._format_message(level, message_str)
59
+
60
+ supports_color = hasattr(sys.stdout, "isatty") and sys.stdout.isatty()
61
+
62
+ if supports_color:
63
+ color = self.colors.get(level, "")
64
+ colored_message = f"{color}{formatted_message}{self.reset_color}"
65
+ else:
66
+ colored_message = formatted_message
67
+
68
+ output_stream = (
69
+ sys.stderr if level.value >= LogLevel.WARNING.value else sys.stdout
70
+ )
71
+ print(colored_message, file=output_stream)
72
+
73
+ def debug(self, message: str):
74
+ self._log(LogLevel.DEBUG, message)
75
+
76
+ def info(self, message: str):
77
+ self._log(LogLevel.INFO, message)
78
+
79
+ def warning(self, message: str):
80
+ self._log(LogLevel.WARNING, message)
81
+
82
+ def error(self, message: str):
83
+ self._log(LogLevel.ERROR, message)
84
+
85
+ def critical(self, message: str):
86
+ self._log(LogLevel.CRITICAL, message)
87
+
88
+ def set_level(self, level: LogLevel):
89
+ self.level = level
90
+
91
+
92
+ _default_logger = Logger()
93
+
94
+
95
+ def get_logger(name: str = "Logger", level: LogLevel = LogLevel.INFO) -> Logger:
96
+ return Logger(name, level)
97
+
98
+
99
+ def debug(message: str):
100
+ _default_logger.debug(message)
101
+
102
+
103
+ def info(message: str):
104
+ _default_logger.info(message)
105
+
106
+
107
+ def warning(message: str):
108
+ _default_logger.warning(message)
109
+
110
+
111
+ def error(message: str):
112
+ _default_logger.error(message)
113
+
114
+
115
+ def critical(message: str):
116
+ _default_logger.critical(message)
117
+
118
+
119
+ def set_level(level: LogLevel):
120
+ _default_logger.set_level(level)
121
+
122
+
123
+ __all__ = [
124
+ "get_logger",
125
+ "set_level",
126
+ "debug",
127
+ "info",
128
+ "warning",
129
+ "error",
130
+ "critical",
131
+ "LogLevel",
132
+ ]