airbyte-agent-greenhouse 0.17.48__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.
- airbyte_agent_greenhouse/__init__.py +105 -0
- airbyte_agent_greenhouse/_vendored/__init__.py +1 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/__init__.py +82 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/auth_strategies.py +1120 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/auth_template.py +135 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/cloud_utils/__init__.py +5 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/cloud_utils/client.py +213 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/connector_model_loader.py +965 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/constants.py +78 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/exceptions.py +23 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/executor/__init__.py +31 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/executor/hosted_executor.py +196 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/executor/local_executor.py +1724 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/executor/models.py +190 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/extensions.py +693 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/http/__init__.py +37 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/http/adapters/__init__.py +9 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/http/adapters/httpx_adapter.py +251 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/http/config.py +98 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/http/exceptions.py +119 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/http/protocols.py +114 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/http/response.py +104 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/http_client.py +693 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/introspection.py +262 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/logging/__init__.py +11 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/logging/logger.py +273 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/logging/types.py +93 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/observability/__init__.py +11 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/observability/config.py +179 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/observability/models.py +19 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/observability/redactor.py +81 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/observability/session.py +103 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/performance/__init__.py +6 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/performance/instrumentation.py +57 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/performance/metrics.py +93 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/schema/__init__.py +75 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/schema/base.py +164 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/schema/components.py +239 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/schema/connector.py +120 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/schema/extensions.py +230 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/schema/operations.py +146 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/schema/security.py +223 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/secrets.py +182 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/telemetry/__init__.py +10 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/telemetry/config.py +32 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/telemetry/events.py +59 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/telemetry/tracker.py +155 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/types.py +245 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/utils.py +60 -0
- airbyte_agent_greenhouse/_vendored/connector_sdk/validation.py +828 -0
- airbyte_agent_greenhouse/connector.py +1391 -0
- airbyte_agent_greenhouse/connector_model.py +2356 -0
- airbyte_agent_greenhouse/models.py +281 -0
- airbyte_agent_greenhouse/types.py +136 -0
- airbyte_agent_greenhouse-0.17.48.dist-info/METADATA +116 -0
- airbyte_agent_greenhouse-0.17.48.dist-info/RECORD +57 -0
- airbyte_agent_greenhouse-0.17.48.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,1391 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Greenhouse connector.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Callable, TypeVar, AsyncIterator, overload
|
|
9
|
+
try:
|
|
10
|
+
from typing import Literal
|
|
11
|
+
except ImportError:
|
|
12
|
+
from typing_extensions import Literal
|
|
13
|
+
|
|
14
|
+
from .connector_model import GreenhouseConnectorModel
|
|
15
|
+
from ._vendored.connector_sdk.introspection import describe_entities, generate_tool_description
|
|
16
|
+
from .types import (
|
|
17
|
+
ApplicationAttachmentDownloadParams,
|
|
18
|
+
ApplicationsGetParams,
|
|
19
|
+
ApplicationsListParams,
|
|
20
|
+
CandidateAttachmentDownloadParams,
|
|
21
|
+
CandidatesGetParams,
|
|
22
|
+
CandidatesListParams,
|
|
23
|
+
DepartmentsGetParams,
|
|
24
|
+
DepartmentsListParams,
|
|
25
|
+
JobPostsGetParams,
|
|
26
|
+
JobPostsListParams,
|
|
27
|
+
JobsGetParams,
|
|
28
|
+
JobsListParams,
|
|
29
|
+
OffersGetParams,
|
|
30
|
+
OffersListParams,
|
|
31
|
+
OfficesGetParams,
|
|
32
|
+
OfficesListParams,
|
|
33
|
+
ScheduledInterviewsGetParams,
|
|
34
|
+
ScheduledInterviewsListParams,
|
|
35
|
+
SourcesListParams,
|
|
36
|
+
UsersGetParams,
|
|
37
|
+
UsersListParams,
|
|
38
|
+
)
|
|
39
|
+
if TYPE_CHECKING:
|
|
40
|
+
from .models import GreenhouseAuthConfig
|
|
41
|
+
# Import response models and envelope models at runtime
|
|
42
|
+
from .models import (
|
|
43
|
+
GreenhouseExecuteResult,
|
|
44
|
+
GreenhouseExecuteResultWithMeta,
|
|
45
|
+
CandidatesListResult,
|
|
46
|
+
ApplicationsListResult,
|
|
47
|
+
JobsListResult,
|
|
48
|
+
OffersListResult,
|
|
49
|
+
UsersListResult,
|
|
50
|
+
DepartmentsListResult,
|
|
51
|
+
OfficesListResult,
|
|
52
|
+
JobPostsListResult,
|
|
53
|
+
SourcesListResult,
|
|
54
|
+
ScheduledInterviewsListResult,
|
|
55
|
+
Application,
|
|
56
|
+
Candidate,
|
|
57
|
+
Department,
|
|
58
|
+
Job,
|
|
59
|
+
JobPost,
|
|
60
|
+
Offer,
|
|
61
|
+
Office,
|
|
62
|
+
ScheduledInterview,
|
|
63
|
+
Source,
|
|
64
|
+
User,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# TypeVar for decorator type preservation
|
|
68
|
+
_F = TypeVar("_F", bound=Callable[..., Any])
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class GreenhouseConnector:
|
|
73
|
+
"""
|
|
74
|
+
Type-safe Greenhouse API connector.
|
|
75
|
+
|
|
76
|
+
Auto-generated from OpenAPI specification with full type safety.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
connector_name = "greenhouse"
|
|
80
|
+
connector_version = "0.1.2"
|
|
81
|
+
vendored_sdk_version = "0.1.0" # Version of vendored connector-sdk
|
|
82
|
+
|
|
83
|
+
# Map of (entity, action) -> needs_envelope for envelope wrapping decision
|
|
84
|
+
_ENVELOPE_MAP = {
|
|
85
|
+
("candidates", "list"): True,
|
|
86
|
+
("candidates", "get"): None,
|
|
87
|
+
("applications", "list"): True,
|
|
88
|
+
("applications", "get"): None,
|
|
89
|
+
("jobs", "list"): True,
|
|
90
|
+
("jobs", "get"): None,
|
|
91
|
+
("offers", "list"): True,
|
|
92
|
+
("offers", "get"): None,
|
|
93
|
+
("users", "list"): True,
|
|
94
|
+
("users", "get"): None,
|
|
95
|
+
("departments", "list"): True,
|
|
96
|
+
("departments", "get"): None,
|
|
97
|
+
("offices", "list"): True,
|
|
98
|
+
("offices", "get"): None,
|
|
99
|
+
("job_posts", "list"): True,
|
|
100
|
+
("job_posts", "get"): None,
|
|
101
|
+
("sources", "list"): True,
|
|
102
|
+
("scheduled_interviews", "list"): True,
|
|
103
|
+
("scheduled_interviews", "get"): None,
|
|
104
|
+
("application_attachment", "download"): None,
|
|
105
|
+
("candidate_attachment", "download"): None,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# Map of (entity, action) -> {python_param_name: api_param_name}
|
|
109
|
+
# Used to convert snake_case TypedDict keys to API parameter names in execute()
|
|
110
|
+
_PARAM_MAP = {
|
|
111
|
+
('candidates', 'list'): {'per_page': 'per_page', 'page': 'page'},
|
|
112
|
+
('candidates', 'get'): {'id': 'id'},
|
|
113
|
+
('applications', 'list'): {'per_page': 'per_page', 'page': 'page', 'created_before': 'created_before', 'created_after': 'created_after', 'last_activity_after': 'last_activity_after', 'job_id': 'job_id', 'status': 'status'},
|
|
114
|
+
('applications', 'get'): {'id': 'id'},
|
|
115
|
+
('jobs', 'list'): {'per_page': 'per_page', 'page': 'page'},
|
|
116
|
+
('jobs', 'get'): {'id': 'id'},
|
|
117
|
+
('offers', 'list'): {'per_page': 'per_page', 'page': 'page', 'created_before': 'created_before', 'created_after': 'created_after', 'resolved_after': 'resolved_after'},
|
|
118
|
+
('offers', 'get'): {'id': 'id'},
|
|
119
|
+
('users', 'list'): {'per_page': 'per_page', 'page': 'page', 'created_before': 'created_before', 'created_after': 'created_after', 'updated_before': 'updated_before', 'updated_after': 'updated_after'},
|
|
120
|
+
('users', 'get'): {'id': 'id'},
|
|
121
|
+
('departments', 'list'): {'per_page': 'per_page', 'page': 'page'},
|
|
122
|
+
('departments', 'get'): {'id': 'id'},
|
|
123
|
+
('offices', 'list'): {'per_page': 'per_page', 'page': 'page'},
|
|
124
|
+
('offices', 'get'): {'id': 'id'},
|
|
125
|
+
('job_posts', 'list'): {'per_page': 'per_page', 'page': 'page', 'live': 'live', 'active': 'active'},
|
|
126
|
+
('job_posts', 'get'): {'id': 'id'},
|
|
127
|
+
('sources', 'list'): {'per_page': 'per_page', 'page': 'page'},
|
|
128
|
+
('scheduled_interviews', 'list'): {'per_page': 'per_page', 'page': 'page', 'created_before': 'created_before', 'created_after': 'created_after', 'updated_before': 'updated_before', 'updated_after': 'updated_after', 'starts_after': 'starts_after', 'ends_before': 'ends_before'},
|
|
129
|
+
('scheduled_interviews', 'get'): {'id': 'id'},
|
|
130
|
+
('application_attachment', 'download'): {'id': 'id', 'attachment_index': 'attachment_index', 'range_header': 'range_header'},
|
|
131
|
+
('candidate_attachment', 'download'): {'id': 'id', 'attachment_index': 'attachment_index', 'range_header': 'range_header'},
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
def __init__(
|
|
135
|
+
self,
|
|
136
|
+
auth_config: GreenhouseAuthConfig | None = None,
|
|
137
|
+
external_user_id: str | None = None,
|
|
138
|
+
airbyte_client_id: str | None = None,
|
|
139
|
+
airbyte_client_secret: str | None = None,
|
|
140
|
+
on_token_refresh: Any | None = None ):
|
|
141
|
+
"""
|
|
142
|
+
Initialize a new greenhouse connector instance.
|
|
143
|
+
|
|
144
|
+
Supports both local and hosted execution modes:
|
|
145
|
+
- Local mode: Provide `auth_config` for direct API calls
|
|
146
|
+
- Hosted mode: Provide `external_user_id`, `airbyte_client_id`, and `airbyte_client_secret` for hosted execution
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
auth_config: Typed authentication configuration (required for local mode)
|
|
150
|
+
external_user_id: External user ID (required for hosted mode)
|
|
151
|
+
airbyte_client_id: Airbyte OAuth client ID (required for hosted mode)
|
|
152
|
+
airbyte_client_secret: Airbyte OAuth client secret (required for hosted mode)
|
|
153
|
+
on_token_refresh: Optional callback for OAuth2 token refresh persistence.
|
|
154
|
+
Called with new_tokens dict when tokens are refreshed. Can be sync or async.
|
|
155
|
+
Example: lambda tokens: save_to_database(tokens)
|
|
156
|
+
Examples:
|
|
157
|
+
# Local mode (direct API calls)
|
|
158
|
+
connector = GreenhouseConnector(auth_config=GreenhouseAuthConfig(api_key="..."))
|
|
159
|
+
# Hosted mode (executed on Airbyte cloud)
|
|
160
|
+
connector = GreenhouseConnector(
|
|
161
|
+
external_user_id="user-123",
|
|
162
|
+
airbyte_client_id="client_abc123",
|
|
163
|
+
airbyte_client_secret="secret_xyz789"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Local mode with OAuth2 token refresh callback
|
|
167
|
+
def save_tokens(new_tokens: dict) -> None:
|
|
168
|
+
# Persist updated tokens to your storage (file, database, etc.)
|
|
169
|
+
with open("tokens.json", "w") as f:
|
|
170
|
+
json.dump(new_tokens, f)
|
|
171
|
+
|
|
172
|
+
connector = GreenhouseConnector(
|
|
173
|
+
auth_config=GreenhouseAuthConfig(access_token="...", refresh_token="..."),
|
|
174
|
+
on_token_refresh=save_tokens
|
|
175
|
+
)
|
|
176
|
+
"""
|
|
177
|
+
# Hosted mode: external_user_id, airbyte_client_id, and airbyte_client_secret provided
|
|
178
|
+
if external_user_id and airbyte_client_id and airbyte_client_secret:
|
|
179
|
+
from ._vendored.connector_sdk.executor import HostedExecutor
|
|
180
|
+
self._executor = HostedExecutor(
|
|
181
|
+
external_user_id=external_user_id,
|
|
182
|
+
airbyte_client_id=airbyte_client_id,
|
|
183
|
+
airbyte_client_secret=airbyte_client_secret,
|
|
184
|
+
connector_definition_id=str(GreenhouseConnectorModel.id),
|
|
185
|
+
)
|
|
186
|
+
else:
|
|
187
|
+
# Local mode: auth_config required
|
|
188
|
+
if not auth_config:
|
|
189
|
+
raise ValueError(
|
|
190
|
+
"Either provide (external_user_id, airbyte_client_id, airbyte_client_secret) for hosted mode "
|
|
191
|
+
"or auth_config for local mode"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
from ._vendored.connector_sdk.executor import LocalExecutor
|
|
195
|
+
|
|
196
|
+
# Build config_values dict from server variables
|
|
197
|
+
config_values = None
|
|
198
|
+
|
|
199
|
+
self._executor = LocalExecutor(
|
|
200
|
+
model=GreenhouseConnectorModel,
|
|
201
|
+
auth_config=auth_config.model_dump() if auth_config else None,
|
|
202
|
+
config_values=config_values,
|
|
203
|
+
on_token_refresh=on_token_refresh
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Update base_url with server variables if provided
|
|
207
|
+
|
|
208
|
+
# Initialize entity query objects
|
|
209
|
+
self.candidates = CandidatesQuery(self)
|
|
210
|
+
self.applications = ApplicationsQuery(self)
|
|
211
|
+
self.jobs = JobsQuery(self)
|
|
212
|
+
self.offers = OffersQuery(self)
|
|
213
|
+
self.users = UsersQuery(self)
|
|
214
|
+
self.departments = DepartmentsQuery(self)
|
|
215
|
+
self.offices = OfficesQuery(self)
|
|
216
|
+
self.job_posts = JobPostsQuery(self)
|
|
217
|
+
self.sources = SourcesQuery(self)
|
|
218
|
+
self.scheduled_interviews = ScheduledInterviewsQuery(self)
|
|
219
|
+
self.application_attachment = ApplicationAttachmentQuery(self)
|
|
220
|
+
self.candidate_attachment = CandidateAttachmentQuery(self)
|
|
221
|
+
|
|
222
|
+
# ===== TYPED EXECUTE METHOD (Recommended Interface) =====
|
|
223
|
+
|
|
224
|
+
@overload
|
|
225
|
+
async def execute(
|
|
226
|
+
self,
|
|
227
|
+
entity: Literal["candidates"],
|
|
228
|
+
action: Literal["list"],
|
|
229
|
+
params: "CandidatesListParams"
|
|
230
|
+
) -> "CandidatesListResult": ...
|
|
231
|
+
|
|
232
|
+
@overload
|
|
233
|
+
async def execute(
|
|
234
|
+
self,
|
|
235
|
+
entity: Literal["candidates"],
|
|
236
|
+
action: Literal["get"],
|
|
237
|
+
params: "CandidatesGetParams"
|
|
238
|
+
) -> "Candidate": ...
|
|
239
|
+
|
|
240
|
+
@overload
|
|
241
|
+
async def execute(
|
|
242
|
+
self,
|
|
243
|
+
entity: Literal["applications"],
|
|
244
|
+
action: Literal["list"],
|
|
245
|
+
params: "ApplicationsListParams"
|
|
246
|
+
) -> "ApplicationsListResult": ...
|
|
247
|
+
|
|
248
|
+
@overload
|
|
249
|
+
async def execute(
|
|
250
|
+
self,
|
|
251
|
+
entity: Literal["applications"],
|
|
252
|
+
action: Literal["get"],
|
|
253
|
+
params: "ApplicationsGetParams"
|
|
254
|
+
) -> "Application": ...
|
|
255
|
+
|
|
256
|
+
@overload
|
|
257
|
+
async def execute(
|
|
258
|
+
self,
|
|
259
|
+
entity: Literal["jobs"],
|
|
260
|
+
action: Literal["list"],
|
|
261
|
+
params: "JobsListParams"
|
|
262
|
+
) -> "JobsListResult": ...
|
|
263
|
+
|
|
264
|
+
@overload
|
|
265
|
+
async def execute(
|
|
266
|
+
self,
|
|
267
|
+
entity: Literal["jobs"],
|
|
268
|
+
action: Literal["get"],
|
|
269
|
+
params: "JobsGetParams"
|
|
270
|
+
) -> "Job": ...
|
|
271
|
+
|
|
272
|
+
@overload
|
|
273
|
+
async def execute(
|
|
274
|
+
self,
|
|
275
|
+
entity: Literal["offers"],
|
|
276
|
+
action: Literal["list"],
|
|
277
|
+
params: "OffersListParams"
|
|
278
|
+
) -> "OffersListResult": ...
|
|
279
|
+
|
|
280
|
+
@overload
|
|
281
|
+
async def execute(
|
|
282
|
+
self,
|
|
283
|
+
entity: Literal["offers"],
|
|
284
|
+
action: Literal["get"],
|
|
285
|
+
params: "OffersGetParams"
|
|
286
|
+
) -> "Offer": ...
|
|
287
|
+
|
|
288
|
+
@overload
|
|
289
|
+
async def execute(
|
|
290
|
+
self,
|
|
291
|
+
entity: Literal["users"],
|
|
292
|
+
action: Literal["list"],
|
|
293
|
+
params: "UsersListParams"
|
|
294
|
+
) -> "UsersListResult": ...
|
|
295
|
+
|
|
296
|
+
@overload
|
|
297
|
+
async def execute(
|
|
298
|
+
self,
|
|
299
|
+
entity: Literal["users"],
|
|
300
|
+
action: Literal["get"],
|
|
301
|
+
params: "UsersGetParams"
|
|
302
|
+
) -> "User": ...
|
|
303
|
+
|
|
304
|
+
@overload
|
|
305
|
+
async def execute(
|
|
306
|
+
self,
|
|
307
|
+
entity: Literal["departments"],
|
|
308
|
+
action: Literal["list"],
|
|
309
|
+
params: "DepartmentsListParams"
|
|
310
|
+
) -> "DepartmentsListResult": ...
|
|
311
|
+
|
|
312
|
+
@overload
|
|
313
|
+
async def execute(
|
|
314
|
+
self,
|
|
315
|
+
entity: Literal["departments"],
|
|
316
|
+
action: Literal["get"],
|
|
317
|
+
params: "DepartmentsGetParams"
|
|
318
|
+
) -> "Department": ...
|
|
319
|
+
|
|
320
|
+
@overload
|
|
321
|
+
async def execute(
|
|
322
|
+
self,
|
|
323
|
+
entity: Literal["offices"],
|
|
324
|
+
action: Literal["list"],
|
|
325
|
+
params: "OfficesListParams"
|
|
326
|
+
) -> "OfficesListResult": ...
|
|
327
|
+
|
|
328
|
+
@overload
|
|
329
|
+
async def execute(
|
|
330
|
+
self,
|
|
331
|
+
entity: Literal["offices"],
|
|
332
|
+
action: Literal["get"],
|
|
333
|
+
params: "OfficesGetParams"
|
|
334
|
+
) -> "Office": ...
|
|
335
|
+
|
|
336
|
+
@overload
|
|
337
|
+
async def execute(
|
|
338
|
+
self,
|
|
339
|
+
entity: Literal["job_posts"],
|
|
340
|
+
action: Literal["list"],
|
|
341
|
+
params: "JobPostsListParams"
|
|
342
|
+
) -> "JobPostsListResult": ...
|
|
343
|
+
|
|
344
|
+
@overload
|
|
345
|
+
async def execute(
|
|
346
|
+
self,
|
|
347
|
+
entity: Literal["job_posts"],
|
|
348
|
+
action: Literal["get"],
|
|
349
|
+
params: "JobPostsGetParams"
|
|
350
|
+
) -> "JobPost": ...
|
|
351
|
+
|
|
352
|
+
@overload
|
|
353
|
+
async def execute(
|
|
354
|
+
self,
|
|
355
|
+
entity: Literal["sources"],
|
|
356
|
+
action: Literal["list"],
|
|
357
|
+
params: "SourcesListParams"
|
|
358
|
+
) -> "SourcesListResult": ...
|
|
359
|
+
|
|
360
|
+
@overload
|
|
361
|
+
async def execute(
|
|
362
|
+
self,
|
|
363
|
+
entity: Literal["scheduled_interviews"],
|
|
364
|
+
action: Literal["list"],
|
|
365
|
+
params: "ScheduledInterviewsListParams"
|
|
366
|
+
) -> "ScheduledInterviewsListResult": ...
|
|
367
|
+
|
|
368
|
+
@overload
|
|
369
|
+
async def execute(
|
|
370
|
+
self,
|
|
371
|
+
entity: Literal["scheduled_interviews"],
|
|
372
|
+
action: Literal["get"],
|
|
373
|
+
params: "ScheduledInterviewsGetParams"
|
|
374
|
+
) -> "ScheduledInterview": ...
|
|
375
|
+
|
|
376
|
+
@overload
|
|
377
|
+
async def execute(
|
|
378
|
+
self,
|
|
379
|
+
entity: Literal["application_attachment"],
|
|
380
|
+
action: Literal["download"],
|
|
381
|
+
params: "ApplicationAttachmentDownloadParams"
|
|
382
|
+
) -> "AsyncIterator[bytes]": ...
|
|
383
|
+
|
|
384
|
+
@overload
|
|
385
|
+
async def execute(
|
|
386
|
+
self,
|
|
387
|
+
entity: Literal["candidate_attachment"],
|
|
388
|
+
action: Literal["download"],
|
|
389
|
+
params: "CandidateAttachmentDownloadParams"
|
|
390
|
+
) -> "AsyncIterator[bytes]": ...
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
@overload
|
|
394
|
+
async def execute(
|
|
395
|
+
self,
|
|
396
|
+
entity: str,
|
|
397
|
+
action: str,
|
|
398
|
+
params: dict[str, Any]
|
|
399
|
+
) -> GreenhouseExecuteResult[Any] | GreenhouseExecuteResultWithMeta[Any, Any] | Any: ...
|
|
400
|
+
|
|
401
|
+
async def execute(
|
|
402
|
+
self,
|
|
403
|
+
entity: str,
|
|
404
|
+
action: str,
|
|
405
|
+
params: dict[str, Any] | None = None
|
|
406
|
+
) -> Any:
|
|
407
|
+
"""
|
|
408
|
+
Execute an entity operation with full type safety.
|
|
409
|
+
|
|
410
|
+
This is the recommended interface for blessed connectors as it:
|
|
411
|
+
- Uses the same signature as non-blessed connectors
|
|
412
|
+
- Provides full IDE autocomplete for entity/action/params
|
|
413
|
+
- Makes migration from generic to blessed connectors seamless
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
entity: Entity name (e.g., "customers")
|
|
417
|
+
action: Operation action (e.g., "create", "get", "list")
|
|
418
|
+
params: Operation parameters (typed based on entity+action)
|
|
419
|
+
|
|
420
|
+
Returns:
|
|
421
|
+
Typed response based on the operation
|
|
422
|
+
|
|
423
|
+
Example:
|
|
424
|
+
customer = await connector.execute(
|
|
425
|
+
entity="customers",
|
|
426
|
+
action="get",
|
|
427
|
+
params={"id": "cus_123"}
|
|
428
|
+
)
|
|
429
|
+
"""
|
|
430
|
+
from ._vendored.connector_sdk.executor import ExecutionConfig
|
|
431
|
+
|
|
432
|
+
# Remap parameter names from snake_case (TypedDict keys) to API parameter names
|
|
433
|
+
if params:
|
|
434
|
+
param_map = self._PARAM_MAP.get((entity, action), {})
|
|
435
|
+
if param_map:
|
|
436
|
+
params = {param_map.get(k, k): v for k, v in params.items()}
|
|
437
|
+
|
|
438
|
+
# Use ExecutionConfig for both local and hosted executors
|
|
439
|
+
config = ExecutionConfig(
|
|
440
|
+
entity=entity,
|
|
441
|
+
action=action,
|
|
442
|
+
params=params
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
result = await self._executor.execute(config)
|
|
446
|
+
|
|
447
|
+
if not result.success:
|
|
448
|
+
raise RuntimeError(f"Execution failed: {result.error}")
|
|
449
|
+
|
|
450
|
+
# Check if this operation has extractors configured
|
|
451
|
+
has_extractors = self._ENVELOPE_MAP.get((entity, action), False)
|
|
452
|
+
|
|
453
|
+
if has_extractors:
|
|
454
|
+
# With extractors - return Pydantic envelope with data and meta
|
|
455
|
+
if result.meta is not None:
|
|
456
|
+
return GreenhouseExecuteResultWithMeta[Any, Any](
|
|
457
|
+
data=result.data,
|
|
458
|
+
meta=result.meta
|
|
459
|
+
)
|
|
460
|
+
else:
|
|
461
|
+
return GreenhouseExecuteResult[Any](data=result.data)
|
|
462
|
+
else:
|
|
463
|
+
# No extractors - return raw response data
|
|
464
|
+
return result.data
|
|
465
|
+
|
|
466
|
+
# ===== INTROSPECTION METHODS =====
|
|
467
|
+
|
|
468
|
+
@classmethod
|
|
469
|
+
def describe(cls, func: _F) -> _F:
|
|
470
|
+
"""
|
|
471
|
+
Decorator that populates a function's docstring with connector capabilities.
|
|
472
|
+
|
|
473
|
+
This class method can be used as a decorator to automatically generate
|
|
474
|
+
comprehensive documentation for AI tool functions.
|
|
475
|
+
|
|
476
|
+
Usage:
|
|
477
|
+
@mcp.tool()
|
|
478
|
+
@GreenhouseConnector.describe
|
|
479
|
+
async def execute(entity: str, action: str, params: dict):
|
|
480
|
+
'''Execute operations.'''
|
|
481
|
+
...
|
|
482
|
+
|
|
483
|
+
The decorated function's __doc__ will be updated with:
|
|
484
|
+
- Available entities and their actions
|
|
485
|
+
- Parameter signatures with required (*) and optional (?) markers
|
|
486
|
+
- Response structure documentation
|
|
487
|
+
- Example questions (if available in OpenAPI spec)
|
|
488
|
+
|
|
489
|
+
Args:
|
|
490
|
+
func: The function to decorate
|
|
491
|
+
|
|
492
|
+
Returns:
|
|
493
|
+
The same function with updated __doc__
|
|
494
|
+
"""
|
|
495
|
+
description = generate_tool_description(GreenhouseConnectorModel)
|
|
496
|
+
|
|
497
|
+
original_doc = func.__doc__ or ""
|
|
498
|
+
if original_doc.strip():
|
|
499
|
+
func.__doc__ = f"{original_doc.strip()}\n{description}"
|
|
500
|
+
else:
|
|
501
|
+
func.__doc__ = description
|
|
502
|
+
|
|
503
|
+
return func
|
|
504
|
+
|
|
505
|
+
def list_entities(self) -> list[dict[str, Any]]:
|
|
506
|
+
"""
|
|
507
|
+
Get structured data about available entities, actions, and parameters.
|
|
508
|
+
|
|
509
|
+
Returns a list of entity descriptions with:
|
|
510
|
+
- entity_name: Name of the entity (e.g., "contacts", "deals")
|
|
511
|
+
- description: Entity description from the first endpoint
|
|
512
|
+
- available_actions: List of actions (e.g., ["list", "get", "create"])
|
|
513
|
+
- parameters: Dict mapping action -> list of parameter dicts
|
|
514
|
+
|
|
515
|
+
Example:
|
|
516
|
+
entities = connector.list_entities()
|
|
517
|
+
for entity in entities:
|
|
518
|
+
print(f"{entity['entity_name']}: {entity['available_actions']}")
|
|
519
|
+
"""
|
|
520
|
+
return describe_entities(GreenhouseConnectorModel)
|
|
521
|
+
|
|
522
|
+
def entity_schema(self, entity: str) -> dict[str, Any] | None:
|
|
523
|
+
"""
|
|
524
|
+
Get the JSON schema for an entity.
|
|
525
|
+
|
|
526
|
+
Args:
|
|
527
|
+
entity: Entity name (e.g., "contacts", "companies")
|
|
528
|
+
|
|
529
|
+
Returns:
|
|
530
|
+
JSON schema dict describing the entity structure, or None if not found.
|
|
531
|
+
|
|
532
|
+
Example:
|
|
533
|
+
schema = connector.entity_schema("contacts")
|
|
534
|
+
if schema:
|
|
535
|
+
print(f"Contact properties: {list(schema.get('properties', {}).keys())}")
|
|
536
|
+
"""
|
|
537
|
+
entity_def = next(
|
|
538
|
+
(e for e in GreenhouseConnectorModel.entities if e.name == entity),
|
|
539
|
+
None
|
|
540
|
+
)
|
|
541
|
+
if entity_def is None:
|
|
542
|
+
logging.getLogger(__name__).warning(
|
|
543
|
+
f"Entity '{entity}' not found. Available entities: "
|
|
544
|
+
f"{[e.name for e in GreenhouseConnectorModel.entities]}"
|
|
545
|
+
)
|
|
546
|
+
return entity_def.entity_schema if entity_def else None
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
class CandidatesQuery:
|
|
551
|
+
"""
|
|
552
|
+
Query class for Candidates entity operations.
|
|
553
|
+
"""
|
|
554
|
+
|
|
555
|
+
def __init__(self, connector: GreenhouseConnector):
|
|
556
|
+
"""Initialize query with connector reference."""
|
|
557
|
+
self._connector = connector
|
|
558
|
+
|
|
559
|
+
async def list(
|
|
560
|
+
self,
|
|
561
|
+
per_page: int | None = None,
|
|
562
|
+
page: int | None = None,
|
|
563
|
+
**kwargs
|
|
564
|
+
) -> CandidatesListResult:
|
|
565
|
+
"""
|
|
566
|
+
Returns a paginated list of all candidates in the organization
|
|
567
|
+
|
|
568
|
+
Args:
|
|
569
|
+
per_page: Number of items to return per page (max 500)
|
|
570
|
+
page: Page number for pagination
|
|
571
|
+
**kwargs: Additional parameters
|
|
572
|
+
|
|
573
|
+
Returns:
|
|
574
|
+
CandidatesListResult
|
|
575
|
+
"""
|
|
576
|
+
params = {k: v for k, v in {
|
|
577
|
+
"per_page": per_page,
|
|
578
|
+
"page": page,
|
|
579
|
+
**kwargs
|
|
580
|
+
}.items() if v is not None}
|
|
581
|
+
|
|
582
|
+
result = await self._connector.execute("candidates", "list", params)
|
|
583
|
+
# Cast generic envelope to concrete typed result
|
|
584
|
+
return CandidatesListResult(
|
|
585
|
+
data=result.data
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
async def get(
|
|
591
|
+
self,
|
|
592
|
+
id: str | None = None,
|
|
593
|
+
**kwargs
|
|
594
|
+
) -> Candidate:
|
|
595
|
+
"""
|
|
596
|
+
Get a single candidate by ID
|
|
597
|
+
|
|
598
|
+
Args:
|
|
599
|
+
id: Candidate ID
|
|
600
|
+
**kwargs: Additional parameters
|
|
601
|
+
|
|
602
|
+
Returns:
|
|
603
|
+
Candidate
|
|
604
|
+
"""
|
|
605
|
+
params = {k: v for k, v in {
|
|
606
|
+
"id": id,
|
|
607
|
+
**kwargs
|
|
608
|
+
}.items() if v is not None}
|
|
609
|
+
|
|
610
|
+
result = await self._connector.execute("candidates", "get", params)
|
|
611
|
+
return result
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
class ApplicationsQuery:
|
|
616
|
+
"""
|
|
617
|
+
Query class for Applications entity operations.
|
|
618
|
+
"""
|
|
619
|
+
|
|
620
|
+
def __init__(self, connector: GreenhouseConnector):
|
|
621
|
+
"""Initialize query with connector reference."""
|
|
622
|
+
self._connector = connector
|
|
623
|
+
|
|
624
|
+
async def list(
|
|
625
|
+
self,
|
|
626
|
+
per_page: int | None = None,
|
|
627
|
+
page: int | None = None,
|
|
628
|
+
created_before: str | None = None,
|
|
629
|
+
created_after: str | None = None,
|
|
630
|
+
last_activity_after: str | None = None,
|
|
631
|
+
job_id: int | None = None,
|
|
632
|
+
status: str | None = None,
|
|
633
|
+
**kwargs
|
|
634
|
+
) -> ApplicationsListResult:
|
|
635
|
+
"""
|
|
636
|
+
Returns a paginated list of all applications
|
|
637
|
+
|
|
638
|
+
Args:
|
|
639
|
+
per_page: Number of items to return per page (max 500)
|
|
640
|
+
page: Page number for pagination
|
|
641
|
+
created_before: Filter by applications created before this timestamp
|
|
642
|
+
created_after: Filter by applications created after this timestamp
|
|
643
|
+
last_activity_after: Filter by applications with activity after this timestamp
|
|
644
|
+
job_id: Filter by job ID
|
|
645
|
+
status: Filter by application status
|
|
646
|
+
**kwargs: Additional parameters
|
|
647
|
+
|
|
648
|
+
Returns:
|
|
649
|
+
ApplicationsListResult
|
|
650
|
+
"""
|
|
651
|
+
params = {k: v for k, v in {
|
|
652
|
+
"per_page": per_page,
|
|
653
|
+
"page": page,
|
|
654
|
+
"created_before": created_before,
|
|
655
|
+
"created_after": created_after,
|
|
656
|
+
"last_activity_after": last_activity_after,
|
|
657
|
+
"job_id": job_id,
|
|
658
|
+
"status": status,
|
|
659
|
+
**kwargs
|
|
660
|
+
}.items() if v is not None}
|
|
661
|
+
|
|
662
|
+
result = await self._connector.execute("applications", "list", params)
|
|
663
|
+
# Cast generic envelope to concrete typed result
|
|
664
|
+
return ApplicationsListResult(
|
|
665
|
+
data=result.data
|
|
666
|
+
)
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
async def get(
|
|
671
|
+
self,
|
|
672
|
+
id: str | None = None,
|
|
673
|
+
**kwargs
|
|
674
|
+
) -> Application:
|
|
675
|
+
"""
|
|
676
|
+
Get a single application by ID
|
|
677
|
+
|
|
678
|
+
Args:
|
|
679
|
+
id: Application ID
|
|
680
|
+
**kwargs: Additional parameters
|
|
681
|
+
|
|
682
|
+
Returns:
|
|
683
|
+
Application
|
|
684
|
+
"""
|
|
685
|
+
params = {k: v for k, v in {
|
|
686
|
+
"id": id,
|
|
687
|
+
**kwargs
|
|
688
|
+
}.items() if v is not None}
|
|
689
|
+
|
|
690
|
+
result = await self._connector.execute("applications", "get", params)
|
|
691
|
+
return result
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
class JobsQuery:
|
|
696
|
+
"""
|
|
697
|
+
Query class for Jobs entity operations.
|
|
698
|
+
"""
|
|
699
|
+
|
|
700
|
+
def __init__(self, connector: GreenhouseConnector):
|
|
701
|
+
"""Initialize query with connector reference."""
|
|
702
|
+
self._connector = connector
|
|
703
|
+
|
|
704
|
+
async def list(
|
|
705
|
+
self,
|
|
706
|
+
per_page: int | None = None,
|
|
707
|
+
page: int | None = None,
|
|
708
|
+
**kwargs
|
|
709
|
+
) -> JobsListResult:
|
|
710
|
+
"""
|
|
711
|
+
Returns a paginated list of all jobs in the organization
|
|
712
|
+
|
|
713
|
+
Args:
|
|
714
|
+
per_page: Number of items to return per page (max 500)
|
|
715
|
+
page: Page number for pagination
|
|
716
|
+
**kwargs: Additional parameters
|
|
717
|
+
|
|
718
|
+
Returns:
|
|
719
|
+
JobsListResult
|
|
720
|
+
"""
|
|
721
|
+
params = {k: v for k, v in {
|
|
722
|
+
"per_page": per_page,
|
|
723
|
+
"page": page,
|
|
724
|
+
**kwargs
|
|
725
|
+
}.items() if v is not None}
|
|
726
|
+
|
|
727
|
+
result = await self._connector.execute("jobs", "list", params)
|
|
728
|
+
# Cast generic envelope to concrete typed result
|
|
729
|
+
return JobsListResult(
|
|
730
|
+
data=result.data
|
|
731
|
+
)
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
async def get(
|
|
736
|
+
self,
|
|
737
|
+
id: str | None = None,
|
|
738
|
+
**kwargs
|
|
739
|
+
) -> Job:
|
|
740
|
+
"""
|
|
741
|
+
Get a single job by ID
|
|
742
|
+
|
|
743
|
+
Args:
|
|
744
|
+
id: Job ID
|
|
745
|
+
**kwargs: Additional parameters
|
|
746
|
+
|
|
747
|
+
Returns:
|
|
748
|
+
Job
|
|
749
|
+
"""
|
|
750
|
+
params = {k: v for k, v in {
|
|
751
|
+
"id": id,
|
|
752
|
+
**kwargs
|
|
753
|
+
}.items() if v is not None}
|
|
754
|
+
|
|
755
|
+
result = await self._connector.execute("jobs", "get", params)
|
|
756
|
+
return result
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
class OffersQuery:
|
|
761
|
+
"""
|
|
762
|
+
Query class for Offers entity operations.
|
|
763
|
+
"""
|
|
764
|
+
|
|
765
|
+
def __init__(self, connector: GreenhouseConnector):
|
|
766
|
+
"""Initialize query with connector reference."""
|
|
767
|
+
self._connector = connector
|
|
768
|
+
|
|
769
|
+
async def list(
|
|
770
|
+
self,
|
|
771
|
+
per_page: int | None = None,
|
|
772
|
+
page: int | None = None,
|
|
773
|
+
created_before: str | None = None,
|
|
774
|
+
created_after: str | None = None,
|
|
775
|
+
resolved_after: str | None = None,
|
|
776
|
+
**kwargs
|
|
777
|
+
) -> OffersListResult:
|
|
778
|
+
"""
|
|
779
|
+
Returns a paginated list of all offers
|
|
780
|
+
|
|
781
|
+
Args:
|
|
782
|
+
per_page: Number of items to return per page (max 500)
|
|
783
|
+
page: Page number for pagination
|
|
784
|
+
created_before: Filter by offers created before this timestamp
|
|
785
|
+
created_after: Filter by offers created after this timestamp
|
|
786
|
+
resolved_after: Filter by offers resolved after this timestamp
|
|
787
|
+
**kwargs: Additional parameters
|
|
788
|
+
|
|
789
|
+
Returns:
|
|
790
|
+
OffersListResult
|
|
791
|
+
"""
|
|
792
|
+
params = {k: v for k, v in {
|
|
793
|
+
"per_page": per_page,
|
|
794
|
+
"page": page,
|
|
795
|
+
"created_before": created_before,
|
|
796
|
+
"created_after": created_after,
|
|
797
|
+
"resolved_after": resolved_after,
|
|
798
|
+
**kwargs
|
|
799
|
+
}.items() if v is not None}
|
|
800
|
+
|
|
801
|
+
result = await self._connector.execute("offers", "list", params)
|
|
802
|
+
# Cast generic envelope to concrete typed result
|
|
803
|
+
return OffersListResult(
|
|
804
|
+
data=result.data
|
|
805
|
+
)
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
async def get(
|
|
810
|
+
self,
|
|
811
|
+
id: str | None = None,
|
|
812
|
+
**kwargs
|
|
813
|
+
) -> Offer:
|
|
814
|
+
"""
|
|
815
|
+
Get a single offer by ID
|
|
816
|
+
|
|
817
|
+
Args:
|
|
818
|
+
id: Offer ID
|
|
819
|
+
**kwargs: Additional parameters
|
|
820
|
+
|
|
821
|
+
Returns:
|
|
822
|
+
Offer
|
|
823
|
+
"""
|
|
824
|
+
params = {k: v for k, v in {
|
|
825
|
+
"id": id,
|
|
826
|
+
**kwargs
|
|
827
|
+
}.items() if v is not None}
|
|
828
|
+
|
|
829
|
+
result = await self._connector.execute("offers", "get", params)
|
|
830
|
+
return result
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
class UsersQuery:
|
|
835
|
+
"""
|
|
836
|
+
Query class for Users entity operations.
|
|
837
|
+
"""
|
|
838
|
+
|
|
839
|
+
def __init__(self, connector: GreenhouseConnector):
|
|
840
|
+
"""Initialize query with connector reference."""
|
|
841
|
+
self._connector = connector
|
|
842
|
+
|
|
843
|
+
async def list(
|
|
844
|
+
self,
|
|
845
|
+
per_page: int | None = None,
|
|
846
|
+
page: int | None = None,
|
|
847
|
+
created_before: str | None = None,
|
|
848
|
+
created_after: str | None = None,
|
|
849
|
+
updated_before: str | None = None,
|
|
850
|
+
updated_after: str | None = None,
|
|
851
|
+
**kwargs
|
|
852
|
+
) -> UsersListResult:
|
|
853
|
+
"""
|
|
854
|
+
Returns a paginated list of all users
|
|
855
|
+
|
|
856
|
+
Args:
|
|
857
|
+
per_page: Number of items to return per page (max 500)
|
|
858
|
+
page: Page number for pagination
|
|
859
|
+
created_before: Filter by users created before this timestamp
|
|
860
|
+
created_after: Filter by users created after this timestamp
|
|
861
|
+
updated_before: Filter by users updated before this timestamp
|
|
862
|
+
updated_after: Filter by users updated after this timestamp
|
|
863
|
+
**kwargs: Additional parameters
|
|
864
|
+
|
|
865
|
+
Returns:
|
|
866
|
+
UsersListResult
|
|
867
|
+
"""
|
|
868
|
+
params = {k: v for k, v in {
|
|
869
|
+
"per_page": per_page,
|
|
870
|
+
"page": page,
|
|
871
|
+
"created_before": created_before,
|
|
872
|
+
"created_after": created_after,
|
|
873
|
+
"updated_before": updated_before,
|
|
874
|
+
"updated_after": updated_after,
|
|
875
|
+
**kwargs
|
|
876
|
+
}.items() if v is not None}
|
|
877
|
+
|
|
878
|
+
result = await self._connector.execute("users", "list", params)
|
|
879
|
+
# Cast generic envelope to concrete typed result
|
|
880
|
+
return UsersListResult(
|
|
881
|
+
data=result.data
|
|
882
|
+
)
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
async def get(
|
|
887
|
+
self,
|
|
888
|
+
id: str | None = None,
|
|
889
|
+
**kwargs
|
|
890
|
+
) -> User:
|
|
891
|
+
"""
|
|
892
|
+
Get a single user by ID
|
|
893
|
+
|
|
894
|
+
Args:
|
|
895
|
+
id: User ID
|
|
896
|
+
**kwargs: Additional parameters
|
|
897
|
+
|
|
898
|
+
Returns:
|
|
899
|
+
User
|
|
900
|
+
"""
|
|
901
|
+
params = {k: v for k, v in {
|
|
902
|
+
"id": id,
|
|
903
|
+
**kwargs
|
|
904
|
+
}.items() if v is not None}
|
|
905
|
+
|
|
906
|
+
result = await self._connector.execute("users", "get", params)
|
|
907
|
+
return result
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
class DepartmentsQuery:
|
|
912
|
+
"""
|
|
913
|
+
Query class for Departments entity operations.
|
|
914
|
+
"""
|
|
915
|
+
|
|
916
|
+
def __init__(self, connector: GreenhouseConnector):
|
|
917
|
+
"""Initialize query with connector reference."""
|
|
918
|
+
self._connector = connector
|
|
919
|
+
|
|
920
|
+
async def list(
|
|
921
|
+
self,
|
|
922
|
+
per_page: int | None = None,
|
|
923
|
+
page: int | None = None,
|
|
924
|
+
**kwargs
|
|
925
|
+
) -> DepartmentsListResult:
|
|
926
|
+
"""
|
|
927
|
+
Returns a paginated list of all departments
|
|
928
|
+
|
|
929
|
+
Args:
|
|
930
|
+
per_page: Number of items to return per page (max 500)
|
|
931
|
+
page: Page number for pagination
|
|
932
|
+
**kwargs: Additional parameters
|
|
933
|
+
|
|
934
|
+
Returns:
|
|
935
|
+
DepartmentsListResult
|
|
936
|
+
"""
|
|
937
|
+
params = {k: v for k, v in {
|
|
938
|
+
"per_page": per_page,
|
|
939
|
+
"page": page,
|
|
940
|
+
**kwargs
|
|
941
|
+
}.items() if v is not None}
|
|
942
|
+
|
|
943
|
+
result = await self._connector.execute("departments", "list", params)
|
|
944
|
+
# Cast generic envelope to concrete typed result
|
|
945
|
+
return DepartmentsListResult(
|
|
946
|
+
data=result.data
|
|
947
|
+
)
|
|
948
|
+
|
|
949
|
+
|
|
950
|
+
|
|
951
|
+
async def get(
|
|
952
|
+
self,
|
|
953
|
+
id: str | None = None,
|
|
954
|
+
**kwargs
|
|
955
|
+
) -> Department:
|
|
956
|
+
"""
|
|
957
|
+
Get a single department by ID
|
|
958
|
+
|
|
959
|
+
Args:
|
|
960
|
+
id: Department ID
|
|
961
|
+
**kwargs: Additional parameters
|
|
962
|
+
|
|
963
|
+
Returns:
|
|
964
|
+
Department
|
|
965
|
+
"""
|
|
966
|
+
params = {k: v for k, v in {
|
|
967
|
+
"id": id,
|
|
968
|
+
**kwargs
|
|
969
|
+
}.items() if v is not None}
|
|
970
|
+
|
|
971
|
+
result = await self._connector.execute("departments", "get", params)
|
|
972
|
+
return result
|
|
973
|
+
|
|
974
|
+
|
|
975
|
+
|
|
976
|
+
class OfficesQuery:
|
|
977
|
+
"""
|
|
978
|
+
Query class for Offices entity operations.
|
|
979
|
+
"""
|
|
980
|
+
|
|
981
|
+
def __init__(self, connector: GreenhouseConnector):
|
|
982
|
+
"""Initialize query with connector reference."""
|
|
983
|
+
self._connector = connector
|
|
984
|
+
|
|
985
|
+
async def list(
|
|
986
|
+
self,
|
|
987
|
+
per_page: int | None = None,
|
|
988
|
+
page: int | None = None,
|
|
989
|
+
**kwargs
|
|
990
|
+
) -> OfficesListResult:
|
|
991
|
+
"""
|
|
992
|
+
Returns a paginated list of all offices
|
|
993
|
+
|
|
994
|
+
Args:
|
|
995
|
+
per_page: Number of items to return per page (max 500)
|
|
996
|
+
page: Page number for pagination
|
|
997
|
+
**kwargs: Additional parameters
|
|
998
|
+
|
|
999
|
+
Returns:
|
|
1000
|
+
OfficesListResult
|
|
1001
|
+
"""
|
|
1002
|
+
params = {k: v for k, v in {
|
|
1003
|
+
"per_page": per_page,
|
|
1004
|
+
"page": page,
|
|
1005
|
+
**kwargs
|
|
1006
|
+
}.items() if v is not None}
|
|
1007
|
+
|
|
1008
|
+
result = await self._connector.execute("offices", "list", params)
|
|
1009
|
+
# Cast generic envelope to concrete typed result
|
|
1010
|
+
return OfficesListResult(
|
|
1011
|
+
data=result.data
|
|
1012
|
+
)
|
|
1013
|
+
|
|
1014
|
+
|
|
1015
|
+
|
|
1016
|
+
async def get(
|
|
1017
|
+
self,
|
|
1018
|
+
id: str | None = None,
|
|
1019
|
+
**kwargs
|
|
1020
|
+
) -> Office:
|
|
1021
|
+
"""
|
|
1022
|
+
Get a single office by ID
|
|
1023
|
+
|
|
1024
|
+
Args:
|
|
1025
|
+
id: Office ID
|
|
1026
|
+
**kwargs: Additional parameters
|
|
1027
|
+
|
|
1028
|
+
Returns:
|
|
1029
|
+
Office
|
|
1030
|
+
"""
|
|
1031
|
+
params = {k: v for k, v in {
|
|
1032
|
+
"id": id,
|
|
1033
|
+
**kwargs
|
|
1034
|
+
}.items() if v is not None}
|
|
1035
|
+
|
|
1036
|
+
result = await self._connector.execute("offices", "get", params)
|
|
1037
|
+
return result
|
|
1038
|
+
|
|
1039
|
+
|
|
1040
|
+
|
|
1041
|
+
class JobPostsQuery:
|
|
1042
|
+
"""
|
|
1043
|
+
Query class for JobPosts entity operations.
|
|
1044
|
+
"""
|
|
1045
|
+
|
|
1046
|
+
def __init__(self, connector: GreenhouseConnector):
|
|
1047
|
+
"""Initialize query with connector reference."""
|
|
1048
|
+
self._connector = connector
|
|
1049
|
+
|
|
1050
|
+
async def list(
|
|
1051
|
+
self,
|
|
1052
|
+
per_page: int | None = None,
|
|
1053
|
+
page: int | None = None,
|
|
1054
|
+
live: bool | None = None,
|
|
1055
|
+
active: bool | None = None,
|
|
1056
|
+
**kwargs
|
|
1057
|
+
) -> JobPostsListResult:
|
|
1058
|
+
"""
|
|
1059
|
+
Returns a paginated list of all job posts
|
|
1060
|
+
|
|
1061
|
+
Args:
|
|
1062
|
+
per_page: Number of items to return per page (max 500)
|
|
1063
|
+
page: Page number for pagination
|
|
1064
|
+
live: Filter by live status
|
|
1065
|
+
active: Filter by active status
|
|
1066
|
+
**kwargs: Additional parameters
|
|
1067
|
+
|
|
1068
|
+
Returns:
|
|
1069
|
+
JobPostsListResult
|
|
1070
|
+
"""
|
|
1071
|
+
params = {k: v for k, v in {
|
|
1072
|
+
"per_page": per_page,
|
|
1073
|
+
"page": page,
|
|
1074
|
+
"live": live,
|
|
1075
|
+
"active": active,
|
|
1076
|
+
**kwargs
|
|
1077
|
+
}.items() if v is not None}
|
|
1078
|
+
|
|
1079
|
+
result = await self._connector.execute("job_posts", "list", params)
|
|
1080
|
+
# Cast generic envelope to concrete typed result
|
|
1081
|
+
return JobPostsListResult(
|
|
1082
|
+
data=result.data
|
|
1083
|
+
)
|
|
1084
|
+
|
|
1085
|
+
|
|
1086
|
+
|
|
1087
|
+
async def get(
|
|
1088
|
+
self,
|
|
1089
|
+
id: str | None = None,
|
|
1090
|
+
**kwargs
|
|
1091
|
+
) -> JobPost:
|
|
1092
|
+
"""
|
|
1093
|
+
Get a single job post by ID
|
|
1094
|
+
|
|
1095
|
+
Args:
|
|
1096
|
+
id: Job Post ID
|
|
1097
|
+
**kwargs: Additional parameters
|
|
1098
|
+
|
|
1099
|
+
Returns:
|
|
1100
|
+
JobPost
|
|
1101
|
+
"""
|
|
1102
|
+
params = {k: v for k, v in {
|
|
1103
|
+
"id": id,
|
|
1104
|
+
**kwargs
|
|
1105
|
+
}.items() if v is not None}
|
|
1106
|
+
|
|
1107
|
+
result = await self._connector.execute("job_posts", "get", params)
|
|
1108
|
+
return result
|
|
1109
|
+
|
|
1110
|
+
|
|
1111
|
+
|
|
1112
|
+
class SourcesQuery:
|
|
1113
|
+
"""
|
|
1114
|
+
Query class for Sources entity operations.
|
|
1115
|
+
"""
|
|
1116
|
+
|
|
1117
|
+
def __init__(self, connector: GreenhouseConnector):
|
|
1118
|
+
"""Initialize query with connector reference."""
|
|
1119
|
+
self._connector = connector
|
|
1120
|
+
|
|
1121
|
+
async def list(
|
|
1122
|
+
self,
|
|
1123
|
+
per_page: int | None = None,
|
|
1124
|
+
page: int | None = None,
|
|
1125
|
+
**kwargs
|
|
1126
|
+
) -> SourcesListResult:
|
|
1127
|
+
"""
|
|
1128
|
+
Returns a paginated list of all sources
|
|
1129
|
+
|
|
1130
|
+
Args:
|
|
1131
|
+
per_page: Number of items to return per page (max 500)
|
|
1132
|
+
page: Page number for pagination
|
|
1133
|
+
**kwargs: Additional parameters
|
|
1134
|
+
|
|
1135
|
+
Returns:
|
|
1136
|
+
SourcesListResult
|
|
1137
|
+
"""
|
|
1138
|
+
params = {k: v for k, v in {
|
|
1139
|
+
"per_page": per_page,
|
|
1140
|
+
"page": page,
|
|
1141
|
+
**kwargs
|
|
1142
|
+
}.items() if v is not None}
|
|
1143
|
+
|
|
1144
|
+
result = await self._connector.execute("sources", "list", params)
|
|
1145
|
+
# Cast generic envelope to concrete typed result
|
|
1146
|
+
return SourcesListResult(
|
|
1147
|
+
data=result.data
|
|
1148
|
+
)
|
|
1149
|
+
|
|
1150
|
+
|
|
1151
|
+
|
|
1152
|
+
class ScheduledInterviewsQuery:
|
|
1153
|
+
"""
|
|
1154
|
+
Query class for ScheduledInterviews entity operations.
|
|
1155
|
+
"""
|
|
1156
|
+
|
|
1157
|
+
def __init__(self, connector: GreenhouseConnector):
|
|
1158
|
+
"""Initialize query with connector reference."""
|
|
1159
|
+
self._connector = connector
|
|
1160
|
+
|
|
1161
|
+
async def list(
|
|
1162
|
+
self,
|
|
1163
|
+
per_page: int | None = None,
|
|
1164
|
+
page: int | None = None,
|
|
1165
|
+
created_before: str | None = None,
|
|
1166
|
+
created_after: str | None = None,
|
|
1167
|
+
updated_before: str | None = None,
|
|
1168
|
+
updated_after: str | None = None,
|
|
1169
|
+
starts_after: str | None = None,
|
|
1170
|
+
ends_before: str | None = None,
|
|
1171
|
+
**kwargs
|
|
1172
|
+
) -> ScheduledInterviewsListResult:
|
|
1173
|
+
"""
|
|
1174
|
+
Returns a paginated list of all scheduled interviews
|
|
1175
|
+
|
|
1176
|
+
Args:
|
|
1177
|
+
per_page: Number of items to return per page (max 500)
|
|
1178
|
+
page: Page number for pagination
|
|
1179
|
+
created_before: Filter by interviews created before this timestamp
|
|
1180
|
+
created_after: Filter by interviews created after this timestamp
|
|
1181
|
+
updated_before: Filter by interviews updated before this timestamp
|
|
1182
|
+
updated_after: Filter by interviews updated after this timestamp
|
|
1183
|
+
starts_after: Filter by interviews starting after this timestamp
|
|
1184
|
+
ends_before: Filter by interviews ending before this timestamp
|
|
1185
|
+
**kwargs: Additional parameters
|
|
1186
|
+
|
|
1187
|
+
Returns:
|
|
1188
|
+
ScheduledInterviewsListResult
|
|
1189
|
+
"""
|
|
1190
|
+
params = {k: v for k, v in {
|
|
1191
|
+
"per_page": per_page,
|
|
1192
|
+
"page": page,
|
|
1193
|
+
"created_before": created_before,
|
|
1194
|
+
"created_after": created_after,
|
|
1195
|
+
"updated_before": updated_before,
|
|
1196
|
+
"updated_after": updated_after,
|
|
1197
|
+
"starts_after": starts_after,
|
|
1198
|
+
"ends_before": ends_before,
|
|
1199
|
+
**kwargs
|
|
1200
|
+
}.items() if v is not None}
|
|
1201
|
+
|
|
1202
|
+
result = await self._connector.execute("scheduled_interviews", "list", params)
|
|
1203
|
+
# Cast generic envelope to concrete typed result
|
|
1204
|
+
return ScheduledInterviewsListResult(
|
|
1205
|
+
data=result.data
|
|
1206
|
+
)
|
|
1207
|
+
|
|
1208
|
+
|
|
1209
|
+
|
|
1210
|
+
async def get(
|
|
1211
|
+
self,
|
|
1212
|
+
id: str | None = None,
|
|
1213
|
+
**kwargs
|
|
1214
|
+
) -> ScheduledInterview:
|
|
1215
|
+
"""
|
|
1216
|
+
Get a single scheduled interview by ID
|
|
1217
|
+
|
|
1218
|
+
Args:
|
|
1219
|
+
id: Scheduled Interview ID
|
|
1220
|
+
**kwargs: Additional parameters
|
|
1221
|
+
|
|
1222
|
+
Returns:
|
|
1223
|
+
ScheduledInterview
|
|
1224
|
+
"""
|
|
1225
|
+
params = {k: v for k, v in {
|
|
1226
|
+
"id": id,
|
|
1227
|
+
**kwargs
|
|
1228
|
+
}.items() if v is not None}
|
|
1229
|
+
|
|
1230
|
+
result = await self._connector.execute("scheduled_interviews", "get", params)
|
|
1231
|
+
return result
|
|
1232
|
+
|
|
1233
|
+
|
|
1234
|
+
|
|
1235
|
+
class ApplicationAttachmentQuery:
|
|
1236
|
+
"""
|
|
1237
|
+
Query class for ApplicationAttachment entity operations.
|
|
1238
|
+
"""
|
|
1239
|
+
|
|
1240
|
+
def __init__(self, connector: GreenhouseConnector):
|
|
1241
|
+
"""Initialize query with connector reference."""
|
|
1242
|
+
self._connector = connector
|
|
1243
|
+
|
|
1244
|
+
async def download(
|
|
1245
|
+
self,
|
|
1246
|
+
attachment_index: str,
|
|
1247
|
+
id: str | None = None,
|
|
1248
|
+
range_header: str | None = None,
|
|
1249
|
+
**kwargs
|
|
1250
|
+
) -> AsyncIterator[bytes]:
|
|
1251
|
+
"""
|
|
1252
|
+
Downloads an attachment (resume, cover letter, etc.) for an application by index.
|
|
1253
|
+
The attachment URL is a temporary signed AWS S3 URL that expires within 7 days.
|
|
1254
|
+
Files should be downloaded immediately after retrieval.
|
|
1255
|
+
|
|
1256
|
+
|
|
1257
|
+
Args:
|
|
1258
|
+
id: Application ID
|
|
1259
|
+
attachment_index: Index of the attachment to download (0-based)
|
|
1260
|
+
range_header: Optional Range header for partial downloads (e.g., 'bytes=0-99')
|
|
1261
|
+
**kwargs: Additional parameters
|
|
1262
|
+
|
|
1263
|
+
Returns:
|
|
1264
|
+
AsyncIterator[bytes]
|
|
1265
|
+
"""
|
|
1266
|
+
params = {k: v for k, v in {
|
|
1267
|
+
"id": id,
|
|
1268
|
+
"attachment_index": attachment_index,
|
|
1269
|
+
"range_header": range_header,
|
|
1270
|
+
**kwargs
|
|
1271
|
+
}.items() if v is not None}
|
|
1272
|
+
|
|
1273
|
+
result = await self._connector.execute("application_attachment", "download", params)
|
|
1274
|
+
return result
|
|
1275
|
+
|
|
1276
|
+
|
|
1277
|
+
async def download_local(
|
|
1278
|
+
self,
|
|
1279
|
+
attachment_index: str,
|
|
1280
|
+
path: str,
|
|
1281
|
+
id: str | None = None,
|
|
1282
|
+
range_header: str | None = None,
|
|
1283
|
+
**kwargs
|
|
1284
|
+
) -> Path:
|
|
1285
|
+
"""
|
|
1286
|
+
Downloads an attachment (resume, cover letter, etc.) for an application by index.
|
|
1287
|
+
The attachment URL is a temporary signed AWS S3 URL that expires within 7 days.
|
|
1288
|
+
Files should be downloaded immediately after retrieval.
|
|
1289
|
+
and save to file.
|
|
1290
|
+
|
|
1291
|
+
Args:
|
|
1292
|
+
id: Application ID
|
|
1293
|
+
attachment_index: Index of the attachment to download (0-based)
|
|
1294
|
+
range_header: Optional Range header for partial downloads (e.g., 'bytes=0-99')
|
|
1295
|
+
path: File path to save downloaded content
|
|
1296
|
+
**kwargs: Additional parameters
|
|
1297
|
+
|
|
1298
|
+
Returns:
|
|
1299
|
+
str: Path to the downloaded file
|
|
1300
|
+
"""
|
|
1301
|
+
from ._vendored.connector_sdk import save_download
|
|
1302
|
+
|
|
1303
|
+
# Get the async iterator
|
|
1304
|
+
content_iterator = await self.download(
|
|
1305
|
+
id=id,
|
|
1306
|
+
attachment_index=attachment_index,
|
|
1307
|
+
range_header=range_header,
|
|
1308
|
+
**kwargs
|
|
1309
|
+
)
|
|
1310
|
+
|
|
1311
|
+
return await save_download(content_iterator, path)
|
|
1312
|
+
|
|
1313
|
+
|
|
1314
|
+
class CandidateAttachmentQuery:
|
|
1315
|
+
"""
|
|
1316
|
+
Query class for CandidateAttachment entity operations.
|
|
1317
|
+
"""
|
|
1318
|
+
|
|
1319
|
+
def __init__(self, connector: GreenhouseConnector):
|
|
1320
|
+
"""Initialize query with connector reference."""
|
|
1321
|
+
self._connector = connector
|
|
1322
|
+
|
|
1323
|
+
async def download(
|
|
1324
|
+
self,
|
|
1325
|
+
attachment_index: str,
|
|
1326
|
+
id: str | None = None,
|
|
1327
|
+
range_header: str | None = None,
|
|
1328
|
+
**kwargs
|
|
1329
|
+
) -> AsyncIterator[bytes]:
|
|
1330
|
+
"""
|
|
1331
|
+
Downloads an attachment (resume, cover letter, etc.) for a candidate by index.
|
|
1332
|
+
The attachment URL is a temporary signed AWS S3 URL that expires within 7 days.
|
|
1333
|
+
Files should be downloaded immediately after retrieval.
|
|
1334
|
+
|
|
1335
|
+
|
|
1336
|
+
Args:
|
|
1337
|
+
id: Candidate ID
|
|
1338
|
+
attachment_index: Index of the attachment to download (0-based)
|
|
1339
|
+
range_header: Optional Range header for partial downloads (e.g., 'bytes=0-99')
|
|
1340
|
+
**kwargs: Additional parameters
|
|
1341
|
+
|
|
1342
|
+
Returns:
|
|
1343
|
+
AsyncIterator[bytes]
|
|
1344
|
+
"""
|
|
1345
|
+
params = {k: v for k, v in {
|
|
1346
|
+
"id": id,
|
|
1347
|
+
"attachment_index": attachment_index,
|
|
1348
|
+
"range_header": range_header,
|
|
1349
|
+
**kwargs
|
|
1350
|
+
}.items() if v is not None}
|
|
1351
|
+
|
|
1352
|
+
result = await self._connector.execute("candidate_attachment", "download", params)
|
|
1353
|
+
return result
|
|
1354
|
+
|
|
1355
|
+
|
|
1356
|
+
async def download_local(
|
|
1357
|
+
self,
|
|
1358
|
+
attachment_index: str,
|
|
1359
|
+
path: str,
|
|
1360
|
+
id: str | None = None,
|
|
1361
|
+
range_header: str | None = None,
|
|
1362
|
+
**kwargs
|
|
1363
|
+
) -> Path:
|
|
1364
|
+
"""
|
|
1365
|
+
Downloads an attachment (resume, cover letter, etc.) for a candidate by index.
|
|
1366
|
+
The attachment URL is a temporary signed AWS S3 URL that expires within 7 days.
|
|
1367
|
+
Files should be downloaded immediately after retrieval.
|
|
1368
|
+
and save to file.
|
|
1369
|
+
|
|
1370
|
+
Args:
|
|
1371
|
+
id: Candidate ID
|
|
1372
|
+
attachment_index: Index of the attachment to download (0-based)
|
|
1373
|
+
range_header: Optional Range header for partial downloads (e.g., 'bytes=0-99')
|
|
1374
|
+
path: File path to save downloaded content
|
|
1375
|
+
**kwargs: Additional parameters
|
|
1376
|
+
|
|
1377
|
+
Returns:
|
|
1378
|
+
str: Path to the downloaded file
|
|
1379
|
+
"""
|
|
1380
|
+
from ._vendored.connector_sdk import save_download
|
|
1381
|
+
|
|
1382
|
+
# Get the async iterator
|
|
1383
|
+
content_iterator = await self.download(
|
|
1384
|
+
id=id,
|
|
1385
|
+
attachment_index=attachment_index,
|
|
1386
|
+
range_header=range_header,
|
|
1387
|
+
**kwargs
|
|
1388
|
+
)
|
|
1389
|
+
|
|
1390
|
+
return await save_download(content_iterator, path)
|
|
1391
|
+
|