swipeflow-api 1.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.
- swipeflow_api/__init__.py +63 -0
- swipeflow_api/client.py +161 -0
- swipeflow_api/exceptions.py +110 -0
- swipeflow_api/models.py +211 -0
- swipeflow_api/py.typed +0 -0
- swipeflow_api/resources.py +412 -0
- swipeflow_api-1.1.0.dist-info/METADATA +568 -0
- swipeflow_api-1.1.0.dist-info/RECORD +11 -0
- swipeflow_api-1.1.0.dist-info/WHEEL +5 -0
- swipeflow_api-1.1.0.dist-info/licenses/LICENSE +15 -0
- swipeflow_api-1.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""SwipeFlow Python SDK for automation workflow approvals.
|
|
2
|
+
|
|
3
|
+
A Pythonic SDK for integrating SwipeFlow approval workflows into your applications.
|
|
4
|
+
|
|
5
|
+
Example:
|
|
6
|
+
>>> from swipeflow_api import SwipeFlowClient
|
|
7
|
+
>>> client = SwipeFlowClient(api_key="your-api-key")
|
|
8
|
+
>>> projects = client.projects.list()
|
|
9
|
+
>>> items = client.items.list(project_id="project-123")
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
__version__ = "1.1.0"
|
|
13
|
+
__author__ = "SwipeFlow"
|
|
14
|
+
__email__ = "support@swipeflow.io"
|
|
15
|
+
__license__ = "ISC"
|
|
16
|
+
|
|
17
|
+
from .client import SwipeFlowClient
|
|
18
|
+
from .models import (
|
|
19
|
+
User,
|
|
20
|
+
Project,
|
|
21
|
+
Item,
|
|
22
|
+
ItemVersion,
|
|
23
|
+
Decision,
|
|
24
|
+
Webhook,
|
|
25
|
+
WebhookEvent,
|
|
26
|
+
ItemStatus,
|
|
27
|
+
DecisionType,
|
|
28
|
+
ProjectRole,
|
|
29
|
+
ContentType,
|
|
30
|
+
ApiKey,
|
|
31
|
+
)
|
|
32
|
+
from .exceptions import (
|
|
33
|
+
SwipeFlowError,
|
|
34
|
+
AuthenticationError,
|
|
35
|
+
AuthorizationError,
|
|
36
|
+
NotFoundError,
|
|
37
|
+
ValidationError,
|
|
38
|
+
ServerError,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
"SwipeFlowClient",
|
|
43
|
+
# Models
|
|
44
|
+
"User",
|
|
45
|
+
"Project",
|
|
46
|
+
"Item",
|
|
47
|
+
"ItemVersion",
|
|
48
|
+
"Decision",
|
|
49
|
+
"Webhook",
|
|
50
|
+
"WebhookEvent",
|
|
51
|
+
"ItemStatus",
|
|
52
|
+
"DecisionType",
|
|
53
|
+
"ProjectRole",
|
|
54
|
+
"ContentType",
|
|
55
|
+
"ApiKey",
|
|
56
|
+
# Exceptions
|
|
57
|
+
"SwipeFlowError",
|
|
58
|
+
"AuthenticationError",
|
|
59
|
+
"AuthorizationError",
|
|
60
|
+
"NotFoundError",
|
|
61
|
+
"ValidationError",
|
|
62
|
+
"ServerError",
|
|
63
|
+
]
|
swipeflow_api/client.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""Main SwipeFlow API client."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
import requests
|
|
5
|
+
from requests.adapters import HTTPAdapter
|
|
6
|
+
from urllib3.util.retry import Retry
|
|
7
|
+
|
|
8
|
+
from .resources import ProjectsResource, ItemsResource, WebhooksResource, ApiKeysResource, AuthResource
|
|
9
|
+
from .exceptions import raise_for_status
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SwipeFlowClient:
|
|
13
|
+
"""Main client for interacting with the SwipeFlow API.
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
>>> client = SwipeFlowClient(api_key="your-api-key")
|
|
17
|
+
>>> projects = client.projects.list()
|
|
18
|
+
>>> items = client.items.list(project_id="project-123")
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
DEFAULT_BASE_URL = "https://api.swipeflow.io"
|
|
22
|
+
DEFAULT_TIMEOUT = 30
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
api_key: Optional[str] = None,
|
|
27
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
28
|
+
timeout: int = DEFAULT_TIMEOUT,
|
|
29
|
+
verify_ssl: bool = True,
|
|
30
|
+
):
|
|
31
|
+
"""Initialize SwipeFlow API client.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
api_key: API key for authentication. If not provided, will look for
|
|
35
|
+
SWIPEFLOW_API_KEY environment variable.
|
|
36
|
+
base_url: Base URL for the API. Defaults to production API.
|
|
37
|
+
timeout: Request timeout in seconds.
|
|
38
|
+
verify_ssl: Whether to verify SSL certificates.
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
ValueError: If no API key is provided and environment variable not set.
|
|
42
|
+
"""
|
|
43
|
+
import os
|
|
44
|
+
|
|
45
|
+
self.api_key = api_key or os.getenv("SWIPEFLOW_API_KEY")
|
|
46
|
+
if not self.api_key:
|
|
47
|
+
raise ValueError(
|
|
48
|
+
"API key not provided. Pass it to SwipeFlowClient(api_key='...') "
|
|
49
|
+
"or set SWIPEFLOW_API_KEY environment variable."
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
self.base_url = base_url.rstrip("/")
|
|
53
|
+
self.timeout = timeout
|
|
54
|
+
self.verify_ssl = verify_ssl
|
|
55
|
+
|
|
56
|
+
self._session = self._create_session()
|
|
57
|
+
|
|
58
|
+
# Initialize resources
|
|
59
|
+
self.projects = ProjectsResource(self)
|
|
60
|
+
self.items = ItemsResource(self)
|
|
61
|
+
self.webhooks = WebhooksResource(self)
|
|
62
|
+
self.api_keys = ApiKeysResource(self)
|
|
63
|
+
self.auth = AuthResource(self)
|
|
64
|
+
|
|
65
|
+
def _create_session(self) -> requests.Session:
|
|
66
|
+
"""Create a requests session with retry strategy.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Configured requests session
|
|
70
|
+
"""
|
|
71
|
+
session = requests.Session()
|
|
72
|
+
|
|
73
|
+
# Set up retry strategy
|
|
74
|
+
retry_strategy = Retry(
|
|
75
|
+
total=3,
|
|
76
|
+
backoff_factor=1,
|
|
77
|
+
status_forcelist=[429, 500, 502, 503, 504],
|
|
78
|
+
allowed_methods=["HEAD", "GET", "OPTIONS", "POST", "PUT", "DELETE"],
|
|
79
|
+
)
|
|
80
|
+
adapter = HTTPAdapter(max_retries=retry_strategy)
|
|
81
|
+
session.mount("https://", adapter)
|
|
82
|
+
session.mount("http://", adapter)
|
|
83
|
+
|
|
84
|
+
# Set headers
|
|
85
|
+
session.headers.update(
|
|
86
|
+
{
|
|
87
|
+
"X-API-Key": self.api_key,
|
|
88
|
+
"Content-Type": "application/json",
|
|
89
|
+
"User-Agent": "swipeflow-python-sdk/1.1.0",
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
return session
|
|
94
|
+
|
|
95
|
+
def request(
|
|
96
|
+
self,
|
|
97
|
+
method: str,
|
|
98
|
+
endpoint: str,
|
|
99
|
+
json_data: Optional[dict] = None,
|
|
100
|
+
params: Optional[dict] = None,
|
|
101
|
+
headers: Optional[dict] = None,
|
|
102
|
+
) -> dict:
|
|
103
|
+
"""Make an HTTP request to the API.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
method: HTTP method (GET, POST, PUT, DELETE, etc.)
|
|
107
|
+
endpoint: API endpoint (without base URL)
|
|
108
|
+
json_data: JSON body data
|
|
109
|
+
params: Query parameters
|
|
110
|
+
headers: Additional headers
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Response JSON as dictionary
|
|
114
|
+
|
|
115
|
+
Raises:
|
|
116
|
+
Various SwipeFlowError subclasses depending on response
|
|
117
|
+
"""
|
|
118
|
+
url = f"{self.base_url}{endpoint}"
|
|
119
|
+
|
|
120
|
+
req_headers = headers or {}
|
|
121
|
+
|
|
122
|
+
response = self._session.request(
|
|
123
|
+
method=method,
|
|
124
|
+
url=url,
|
|
125
|
+
json=json_data,
|
|
126
|
+
params=params,
|
|
127
|
+
headers=req_headers,
|
|
128
|
+
timeout=self.timeout,
|
|
129
|
+
verify=self.verify_ssl,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Handle errors
|
|
133
|
+
if response.status_code >= 400:
|
|
134
|
+
try:
|
|
135
|
+
error_data = response.json()
|
|
136
|
+
message = error_data.get("error", response.text)
|
|
137
|
+
error_code = error_data.get("code")
|
|
138
|
+
details = error_data.get("details")
|
|
139
|
+
except Exception:
|
|
140
|
+
message = response.text or f"HTTP {response.status_code}"
|
|
141
|
+
error_code = None
|
|
142
|
+
details = None
|
|
143
|
+
|
|
144
|
+
raise_for_status(response.status_code, message, error_code, details)
|
|
145
|
+
|
|
146
|
+
# Handle successful response
|
|
147
|
+
if response.text:
|
|
148
|
+
return response.json()
|
|
149
|
+
return {}
|
|
150
|
+
|
|
151
|
+
def close(self) -> None:
|
|
152
|
+
"""Close the client session."""
|
|
153
|
+
self._session.close()
|
|
154
|
+
|
|
155
|
+
def __enter__(self):
|
|
156
|
+
"""Context manager entry."""
|
|
157
|
+
return self
|
|
158
|
+
|
|
159
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
160
|
+
"""Context manager exit."""
|
|
161
|
+
self.close()
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Exception classes for SwipeFlow API client."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SwipeFlowError(Exception):
|
|
7
|
+
"""Base exception for all SwipeFlow API errors."""
|
|
8
|
+
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
message: str,
|
|
12
|
+
status_code: Optional[int] = None,
|
|
13
|
+
error_code: Optional[str] = None,
|
|
14
|
+
details: Optional[Dict[str, Any]] = None,
|
|
15
|
+
):
|
|
16
|
+
self.message = message
|
|
17
|
+
self.status_code = status_code
|
|
18
|
+
self.error_code = error_code
|
|
19
|
+
self.details = details or {}
|
|
20
|
+
super().__init__(self._format_message())
|
|
21
|
+
|
|
22
|
+
def _format_message(self) -> str:
|
|
23
|
+
"""Format the error message."""
|
|
24
|
+
parts = [self.message]
|
|
25
|
+
if self.status_code:
|
|
26
|
+
parts.append(f"(HTTP {self.status_code})")
|
|
27
|
+
if self.error_code:
|
|
28
|
+
parts.append(f"[{self.error_code}]")
|
|
29
|
+
return " ".join(parts)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AuthenticationError(SwipeFlowError):
|
|
33
|
+
"""Raised when authentication fails (401 Unauthorized)."""
|
|
34
|
+
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AuthorizationError(SwipeFlowError):
|
|
39
|
+
"""Raised when user lacks permissions (403 Forbidden)."""
|
|
40
|
+
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class NotFoundError(SwipeFlowError):
|
|
45
|
+
"""Raised when a resource is not found (404 Not Found)."""
|
|
46
|
+
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ValidationError(SwipeFlowError):
|
|
51
|
+
"""Raised when request validation fails (400 Bad Request)."""
|
|
52
|
+
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ConflictError(SwipeFlowError):
|
|
57
|
+
"""Raised when a resource conflict occurs (409 Conflict)."""
|
|
58
|
+
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ServerError(SwipeFlowError):
|
|
63
|
+
"""Raised when the server encounters an error (5xx)."""
|
|
64
|
+
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class NetworkError(SwipeFlowError):
|
|
69
|
+
"""Raised when a network error occurs."""
|
|
70
|
+
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def raise_for_status(
|
|
75
|
+
status_code: int,
|
|
76
|
+
message: str,
|
|
77
|
+
error_code: Optional[str] = None,
|
|
78
|
+
details: Optional[Dict[str, Any]] = None,
|
|
79
|
+
) -> None:
|
|
80
|
+
"""Raise appropriate exception based on status code.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
status_code: HTTP status code
|
|
84
|
+
message: Error message
|
|
85
|
+
error_code: API-specific error code
|
|
86
|
+
details: Additional error details
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
AuthenticationError: For 401 responses
|
|
90
|
+
AuthorizationError: For 403 responses
|
|
91
|
+
NotFoundError: For 404 responses
|
|
92
|
+
ValidationError: For 400 responses
|
|
93
|
+
ConflictError: For 409 responses
|
|
94
|
+
ServerError: For 5xx responses
|
|
95
|
+
SwipeFlowError: For other errors
|
|
96
|
+
"""
|
|
97
|
+
if status_code == 400:
|
|
98
|
+
raise ValidationError(message, status_code, error_code, details)
|
|
99
|
+
elif status_code == 401:
|
|
100
|
+
raise AuthenticationError(message, status_code, error_code, details)
|
|
101
|
+
elif status_code == 403:
|
|
102
|
+
raise AuthorizationError(message, status_code, error_code, details)
|
|
103
|
+
elif status_code == 404:
|
|
104
|
+
raise NotFoundError(message, status_code, error_code, details)
|
|
105
|
+
elif status_code == 409:
|
|
106
|
+
raise ConflictError(message, status_code, error_code, details)
|
|
107
|
+
elif status_code >= 500:
|
|
108
|
+
raise ServerError(message, status_code, error_code, details)
|
|
109
|
+
else:
|
|
110
|
+
raise SwipeFlowError(message, status_code, error_code, details)
|
swipeflow_api/models.py
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Pydantic models for SwipeFlow API resources."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ItemStatus(str, Enum):
|
|
11
|
+
"""Status of an item."""
|
|
12
|
+
|
|
13
|
+
PENDING = "PENDING"
|
|
14
|
+
APPROVED = "APPROVED"
|
|
15
|
+
REJECTED = "REJECTED"
|
|
16
|
+
CHANGE_REQUESTED = "CHANGE_REQUESTED"
|
|
17
|
+
PROCESSED = "PROCESSED"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DecisionType(str, Enum):
|
|
21
|
+
"""Type of decision on an item."""
|
|
22
|
+
|
|
23
|
+
APPROVED = "APPROVED"
|
|
24
|
+
REJECTED = "REJECTED"
|
|
25
|
+
CHANGE_REQUESTED = "CHANGE_REQUESTED"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ProjectRole(str, Enum):
|
|
29
|
+
"""Role in a project."""
|
|
30
|
+
|
|
31
|
+
OWNER = "OWNER"
|
|
32
|
+
ADMIN = "ADMIN"
|
|
33
|
+
EDITOR = "EDITOR"
|
|
34
|
+
VIEWER = "VIEWER"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ContentType(str, Enum):
|
|
38
|
+
"""Type of content."""
|
|
39
|
+
|
|
40
|
+
TEXT = "TEXT"
|
|
41
|
+
HTML = "HTML"
|
|
42
|
+
IMAGE = "IMAGE"
|
|
43
|
+
VIDEO = "VIDEO"
|
|
44
|
+
AUDIO = "AUDIO"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class WebhookEvent(str, Enum):
|
|
48
|
+
"""Types of webhook events."""
|
|
49
|
+
|
|
50
|
+
ITEM_CREATED = "item.created"
|
|
51
|
+
ITEM_UPDATED = "item.updated"
|
|
52
|
+
ITEM_DELETED = "item.deleted"
|
|
53
|
+
ITEM_APPROVED = "item.approved"
|
|
54
|
+
ITEM_REJECTED = "item.rejected"
|
|
55
|
+
ITEM_CHANGE_REQUESTED = "item.change_requested"
|
|
56
|
+
ITEM_PROCESSED = "item.processed"
|
|
57
|
+
PROJECT_TRIGGER = "project.trigger"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class User(BaseModel):
|
|
61
|
+
"""User model."""
|
|
62
|
+
|
|
63
|
+
id: str = Field(..., description="User ID")
|
|
64
|
+
email: str = Field(..., description="Email address")
|
|
65
|
+
name: Optional[str] = Field(None, description="User's full name")
|
|
66
|
+
avatar_url: Optional[str] = Field(None, description="Avatar URL")
|
|
67
|
+
created_at: datetime = Field(..., description="Account creation time")
|
|
68
|
+
updated_at: datetime = Field(..., description="Last update time")
|
|
69
|
+
|
|
70
|
+
class Config:
|
|
71
|
+
use_enum_values = True
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ProjectMember(BaseModel):
|
|
75
|
+
"""Project member information."""
|
|
76
|
+
|
|
77
|
+
user_id: str = Field(..., description="User ID")
|
|
78
|
+
role: ProjectRole = Field(..., description="Role in the project")
|
|
79
|
+
joined_at: datetime = Field(..., description="When user joined")
|
|
80
|
+
|
|
81
|
+
class Config:
|
|
82
|
+
use_enum_values = True
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class Project(BaseModel):
|
|
86
|
+
"""Project model."""
|
|
87
|
+
|
|
88
|
+
id: str = Field(..., description="Project ID")
|
|
89
|
+
name: str = Field(..., description="Project name")
|
|
90
|
+
description: Optional[str] = Field(None, description="Project description")
|
|
91
|
+
owner_id: str = Field(..., description="Owner user ID")
|
|
92
|
+
members: List[ProjectMember] = Field(default_factory=list, description="Project members")
|
|
93
|
+
item_count: int = Field(default=0, description="Number of items")
|
|
94
|
+
webhook_count: int = Field(default=0, description="Number of webhooks")
|
|
95
|
+
created_at: datetime = Field(..., description="Creation time")
|
|
96
|
+
updated_at: datetime = Field(..., description="Last update time")
|
|
97
|
+
|
|
98
|
+
class Config:
|
|
99
|
+
use_enum_values = True
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class ItemContent(BaseModel):
|
|
103
|
+
"""Content of an item."""
|
|
104
|
+
|
|
105
|
+
type: ContentType = Field(..., description="Content type")
|
|
106
|
+
data: Dict[str, Any] = Field(default_factory=dict, description="Content data")
|
|
107
|
+
text: Optional[str] = Field(None, description="Text representation")
|
|
108
|
+
url: Optional[str] = Field(None, description="URL for media content")
|
|
109
|
+
|
|
110
|
+
class Config:
|
|
111
|
+
use_enum_values = True
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class Decision(BaseModel):
|
|
115
|
+
"""Decision on an item."""
|
|
116
|
+
|
|
117
|
+
id: str = Field(..., description="Decision ID")
|
|
118
|
+
type: DecisionType = Field(..., description="Decision type")
|
|
119
|
+
decided_by: str = Field(..., description="User ID who decided")
|
|
120
|
+
decided_at: datetime = Field(..., description="When decision was made")
|
|
121
|
+
comment: Optional[str] = Field(None, description="Optional comment")
|
|
122
|
+
|
|
123
|
+
class Config:
|
|
124
|
+
use_enum_values = True
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class ItemVersion(BaseModel):
|
|
128
|
+
"""Version history of an item."""
|
|
129
|
+
|
|
130
|
+
id: str = Field(..., description="Version ID")
|
|
131
|
+
item_id: str = Field(..., description="Item ID")
|
|
132
|
+
version_number: int = Field(..., description="Version number")
|
|
133
|
+
content: ItemContent = Field(..., description="Content at this version")
|
|
134
|
+
created_at: datetime = Field(..., description="When this version was created")
|
|
135
|
+
|
|
136
|
+
class Config:
|
|
137
|
+
use_enum_values = True
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class Item(BaseModel):
|
|
141
|
+
"""Item model."""
|
|
142
|
+
|
|
143
|
+
id: str = Field(..., description="Item ID")
|
|
144
|
+
project_id: str = Field(..., description="Project ID")
|
|
145
|
+
title: str = Field(..., description="Item title")
|
|
146
|
+
content: ItemContent = Field(..., description="Item content")
|
|
147
|
+
status: ItemStatus = Field(..., description="Current status")
|
|
148
|
+
decisions: List[Decision] = Field(default_factory=list, description="Decisions made on this item")
|
|
149
|
+
version: int = Field(default=1, description="Current version number")
|
|
150
|
+
created_by: str = Field(..., description="User ID who created")
|
|
151
|
+
created_at: datetime = Field(..., description="Creation time")
|
|
152
|
+
updated_at: datetime = Field(..., description="Last update time")
|
|
153
|
+
processed_at: Optional[datetime] = Field(None, description="When item was processed")
|
|
154
|
+
|
|
155
|
+
class Config:
|
|
156
|
+
use_enum_values = True
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class Webhook(BaseModel):
|
|
160
|
+
"""Webhook model."""
|
|
161
|
+
|
|
162
|
+
id: str = Field(..., description="Webhook ID")
|
|
163
|
+
project_id: str = Field(..., description="Project ID")
|
|
164
|
+
name: str = Field(..., description="Webhook name")
|
|
165
|
+
url: str = Field(..., description="Webhook URL")
|
|
166
|
+
type: str = Field(..., description="Webhook type (user or dynamic)")
|
|
167
|
+
events: List[WebhookEvent] = Field(..., description="Events this webhook subscribes to")
|
|
168
|
+
active: bool = Field(default=True, description="Whether webhook is active")
|
|
169
|
+
integration_provider: Optional[str] = Field(None, description="Integration provider")
|
|
170
|
+
created_at: datetime = Field(..., description="Creation time")
|
|
171
|
+
updated_at: datetime = Field(..., description="Last update time")
|
|
172
|
+
|
|
173
|
+
class Config:
|
|
174
|
+
use_enum_values = True
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class ApiKey(BaseModel):
|
|
178
|
+
"""API key model."""
|
|
179
|
+
|
|
180
|
+
id: str = Field(..., description="API key ID")
|
|
181
|
+
name: str = Field(..., description="Key name")
|
|
182
|
+
key: Optional[str] = Field(None, description="The actual key (only on creation)")
|
|
183
|
+
last_used_at: Optional[datetime] = Field(None, description="Last time key was used")
|
|
184
|
+
created_at: datetime = Field(..., description="Creation time")
|
|
185
|
+
|
|
186
|
+
class Config:
|
|
187
|
+
use_enum_values = True
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class PaginatedResponse(BaseModel):
|
|
191
|
+
"""Paginated response wrapper."""
|
|
192
|
+
|
|
193
|
+
items: List[Dict[str, Any]] = Field(default_factory=list, description="Items in this page")
|
|
194
|
+
total: int = Field(default=0, description="Total number of items")
|
|
195
|
+
page: int = Field(default=1, description="Current page number")
|
|
196
|
+
per_page: int = Field(default=20, description="Items per page")
|
|
197
|
+
has_more: bool = Field(default=False, description="Whether more items exist")
|
|
198
|
+
|
|
199
|
+
class Config:
|
|
200
|
+
use_enum_values = True
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class ErrorResponse(BaseModel):
|
|
204
|
+
"""Error response model."""
|
|
205
|
+
|
|
206
|
+
error: str = Field(..., description="Error message")
|
|
207
|
+
code: Optional[str] = Field(None, description="Error code")
|
|
208
|
+
details: Optional[Dict[str, Any]] = Field(None, description="Additional details")
|
|
209
|
+
|
|
210
|
+
class Config:
|
|
211
|
+
use_enum_values = True
|
swipeflow_api/py.typed
ADDED
|
File without changes
|