deutero 0.2.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.
deutero/__init__.py ADDED
@@ -0,0 +1,258 @@
1
+ """Deutero Python SDK — bindings for the Deutero Study Management API.
2
+
3
+ Usage::
4
+
5
+ from deutero import Deutero
6
+
7
+ client = Deutero(api_key="your-api-key")
8
+ project = client.projects.create(name="Onboarding research")
9
+ study = client.studies.create(
10
+ project_id=project.id,
11
+ name="Why new users drop off",
12
+ survey_type="user_experience",
13
+ )
14
+ """
15
+
16
+ from deutero import webhooks
17
+ from deutero.client import AsyncDeutero, Deutero
18
+ from deutero.exceptions import (
19
+ APIError,
20
+ AuthenticationError,
21
+ BadGatewayError,
22
+ ConflictError,
23
+ ConnectionError,
24
+ DeuteroError,
25
+ InsufficientCreditsError,
26
+ InternalServerError,
27
+ NotFoundError,
28
+ PermissionDeniedError,
29
+ RateLimitError,
30
+ TimeoutError,
31
+ ValidationError,
32
+ WebhookVerificationError,
33
+ )
34
+ from deutero.models import (
35
+ AnalysisCategory,
36
+ AnalyzableQuestion,
37
+ AnalyzableQuestionsOut,
38
+ CapturedVariableOut,
39
+ Centroid,
40
+ CharacteristicQuestionOut,
41
+ CharacteristicsOut,
42
+ CharacteristicsSettingsOut,
43
+ CharacteristicValueOut,
44
+ ClusteringOut,
45
+ ClusterPoints,
46
+ CreditBalanceOut,
47
+ CreditCheckOut,
48
+ DecisionOutcomeOut,
49
+ EmbedKeyListOut,
50
+ EmbedMetadataItem,
51
+ EmbedSnippetOut,
52
+ EthicsCheck,
53
+ FlowCheckError,
54
+ FlowDocument,
55
+ FlowEdge,
56
+ FlowNode,
57
+ FlowSignalOut,
58
+ FlowSignalsOut,
59
+ GeneratedQuestionsOut,
60
+ GraphCheckOut,
61
+ GraphModeOut,
62
+ GraphOut,
63
+ InterviewDetailListOut,
64
+ InterviewDetailOut,
65
+ InterviewEffectsOut,
66
+ InterviewListOut,
67
+ InterviewSummary,
68
+ InterviewTranscript,
69
+ IssueLocation,
70
+ IssueOut,
71
+ LanguageIssues,
72
+ LatestValidationOut,
73
+ ModelTier,
74
+ NodeFetchOut,
75
+ NodeType,
76
+ OptimalClustersOut,
77
+ OptionCount,
78
+ OptionsResponsesOut,
79
+ PatchOp,
80
+ PauseOut,
81
+ PersonaGenerateOut,
82
+ PersonaListOut,
83
+ PersonaOut,
84
+ ProjectListOut,
85
+ ProjectOut,
86
+ PublicationStatusOut,
87
+ PublishOut,
88
+ QualificationOut,
89
+ QuestionImageOut,
90
+ QuestionListOut,
91
+ QuestionOut,
92
+ QuestionValidation,
93
+ RecruitmentOut,
94
+ RedundancyCheck,
95
+ ScaleConfig,
96
+ ScaleResponsesOut,
97
+ ScreeningOut,
98
+ ScreeningQuestionOut,
99
+ ScreeningSettingsOut,
100
+ SearchHit,
101
+ SearchMode,
102
+ SearchOut,
103
+ SignalDeliveryOut,
104
+ SimulationListOut,
105
+ SimulationOut,
106
+ SimulationStartOut,
107
+ StudyDraft,
108
+ StudyDraftFromSiteOut,
109
+ StudyListOut,
110
+ StudyOut,
111
+ StudyStatsOut,
112
+ StudySummary,
113
+ StudyType,
114
+ SuccessResponse,
115
+ TranscriptMessage,
116
+ TranscriptOut,
117
+ TranscriptsBulkOut,
118
+ ValidationOut,
119
+ ValidationRunOut,
120
+ WebhookCreatedOut,
121
+ WebhookDeleteOut,
122
+ WebhookDeliveryListOut,
123
+ WebhookDeliveryOut,
124
+ WebhookEventTypeListOut,
125
+ WebhookEventTypeOut,
126
+ WebhookListOut,
127
+ WebhookOut,
128
+ WebhookSecretOut,
129
+ WelcomeDraftOut,
130
+ WelcomeOut,
131
+ WelcomeTranslationListOut,
132
+ WelcomeTranslationOut,
133
+ )
134
+
135
+ __all__ = [
136
+ # Clients
137
+ "Deutero",
138
+ "AsyncDeutero",
139
+ # Exceptions
140
+ "DeuteroError",
141
+ "APIError",
142
+ "AuthenticationError",
143
+ "BadGatewayError",
144
+ "ConflictError",
145
+ "ConnectionError",
146
+ "InsufficientCreditsError",
147
+ "InternalServerError",
148
+ "NotFoundError",
149
+ "PermissionDeniedError",
150
+ "RateLimitError",
151
+ "TimeoutError",
152
+ "ValidationError",
153
+ "WebhookVerificationError",
154
+ # Webhook payload handling
155
+ "webhooks",
156
+ # Enums
157
+ "AnalysisCategory",
158
+ "ModelTier",
159
+ "NodeType",
160
+ "SearchMode",
161
+ "StudyType",
162
+ # Models
163
+ "AnalyzableQuestion",
164
+ "AnalyzableQuestionsOut",
165
+ "CapturedVariableOut",
166
+ "Centroid",
167
+ "CharacteristicQuestionOut",
168
+ "CharacteristicValueOut",
169
+ "CharacteristicsOut",
170
+ "CharacteristicsSettingsOut",
171
+ "ClusterPoints",
172
+ "ClusteringOut",
173
+ "CreditBalanceOut",
174
+ "CreditCheckOut",
175
+ "DecisionOutcomeOut",
176
+ "EmbedKeyListOut",
177
+ "EmbedMetadataItem",
178
+ "EmbedSnippetOut",
179
+ "EthicsCheck",
180
+ "FlowCheckError",
181
+ "FlowDocument",
182
+ "FlowEdge",
183
+ "FlowNode",
184
+ "FlowSignalOut",
185
+ "FlowSignalsOut",
186
+ "GeneratedQuestionsOut",
187
+ "GraphCheckOut",
188
+ "GraphModeOut",
189
+ "GraphOut",
190
+ "InterviewDetailListOut",
191
+ "InterviewDetailOut",
192
+ "InterviewEffectsOut",
193
+ "InterviewListOut",
194
+ "InterviewSummary",
195
+ "InterviewTranscript",
196
+ "IssueLocation",
197
+ "IssueOut",
198
+ "LanguageIssues",
199
+ "LatestValidationOut",
200
+ "NodeFetchOut",
201
+ "OptimalClustersOut",
202
+ "OptionCount",
203
+ "OptionsResponsesOut",
204
+ "PatchOp",
205
+ "PauseOut",
206
+ "PersonaGenerateOut",
207
+ "PersonaListOut",
208
+ "PersonaOut",
209
+ "ProjectListOut",
210
+ "ProjectOut",
211
+ "PublicationStatusOut",
212
+ "PublishOut",
213
+ "QualificationOut",
214
+ "QuestionImageOut",
215
+ "QuestionListOut",
216
+ "QuestionOut",
217
+ "QuestionValidation",
218
+ "RecruitmentOut",
219
+ "RedundancyCheck",
220
+ "ScaleConfig",
221
+ "ScaleResponsesOut",
222
+ "ScreeningOut",
223
+ "ScreeningQuestionOut",
224
+ "ScreeningSettingsOut",
225
+ "SearchHit",
226
+ "SearchOut",
227
+ "SignalDeliveryOut",
228
+ "SimulationListOut",
229
+ "SimulationOut",
230
+ "SimulationStartOut",
231
+ "StudyDraft",
232
+ "StudyDraftFromSiteOut",
233
+ "StudyListOut",
234
+ "StudyOut",
235
+ "StudyStatsOut",
236
+ "StudySummary",
237
+ "SuccessResponse",
238
+ "TranscriptMessage",
239
+ "TranscriptOut",
240
+ "TranscriptsBulkOut",
241
+ "ValidationOut",
242
+ "ValidationRunOut",
243
+ "WebhookCreatedOut",
244
+ "WebhookDeleteOut",
245
+ "WebhookDeliveryListOut",
246
+ "WebhookDeliveryOut",
247
+ "WebhookEventTypeListOut",
248
+ "WebhookEventTypeOut",
249
+ "WebhookListOut",
250
+ "WebhookOut",
251
+ "WebhookSecretOut",
252
+ "WelcomeDraftOut",
253
+ "WelcomeOut",
254
+ "WelcomeTranslationListOut",
255
+ "WelcomeTranslationOut",
256
+ ]
257
+
258
+ __version__ = "0.2.0"
deutero/_http.py ADDED
@@ -0,0 +1,217 @@
1
+ """Low-level HTTP transport for the Deutero SDK.
2
+
3
+ Provides both synchronous and asynchronous HTTP clients backed by httpx.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import enum
9
+ from datetime import datetime
10
+ from typing import Any, Dict, Optional
11
+ from uuid import UUID
12
+
13
+ import httpx
14
+ from pydantic import BaseModel
15
+
16
+ from deutero.exceptions import ConnectionError, TimeoutError, raise_for_status
17
+
18
+ _DEFAULT_TIMEOUT = 120.0
19
+ _DEFAULT_BASE_URL = "https://dashboard.deutero.ai/study-api"
20
+ _USER_AGENT = "deutero-python/0.2.0"
21
+
22
+
23
+ def _build_headers(api_key: str, extra: Optional[Dict[str, str]] = None) -> Dict[str, str]:
24
+ headers = {
25
+ "X-API-Key": api_key,
26
+ "User-Agent": _USER_AGENT,
27
+ "Accept": "application/json",
28
+ }
29
+ if extra:
30
+ headers.update(extra)
31
+ return headers
32
+
33
+
34
+ def _parse_response(response: httpx.Response) -> Any:
35
+ request_id = response.headers.get("x-request-id")
36
+ if response.status_code == 204 or not response.content:
37
+ raise_for_status(response.status_code, None, request_id=request_id)
38
+ return None
39
+ try:
40
+ body = response.json()
41
+ except Exception:
42
+ body = response.text
43
+
44
+ raise_for_status(response.status_code, body, request_id=request_id)
45
+ return body
46
+
47
+
48
+ class SyncHTTPClient:
49
+ """Synchronous HTTP client."""
50
+
51
+ def __init__(
52
+ self,
53
+ *,
54
+ api_key: str,
55
+ base_url: str = _DEFAULT_BASE_URL,
56
+ timeout: float = _DEFAULT_TIMEOUT,
57
+ http_client: Optional[httpx.Client] = None,
58
+ ) -> None:
59
+ self._api_key = api_key
60
+ self._base_url = base_url.rstrip("/")
61
+ self._timeout = timeout
62
+ self._headers = _build_headers(api_key)
63
+ self._owns_client = http_client is None
64
+ self._client = http_client or httpx.Client(
65
+ base_url=self._base_url,
66
+ headers=self._headers,
67
+ timeout=timeout,
68
+ )
69
+
70
+ def request(
71
+ self,
72
+ method: str,
73
+ path: str,
74
+ *,
75
+ json: Optional[Any] = None,
76
+ params: Optional[Dict[str, Any]] = None,
77
+ ) -> Any:
78
+ try:
79
+ response = self._client.request(
80
+ method,
81
+ self._url(path),
82
+ json=json,
83
+ params=_clean_params(params),
84
+ headers=self._headers,
85
+ )
86
+ except httpx.ConnectError as exc:
87
+ raise ConnectionError(f"Failed to connect to {self._base_url}: {exc}") from exc
88
+ except httpx.TimeoutException as exc:
89
+ raise TimeoutError(f"Request to {path} timed out after {self._timeout}s") from exc
90
+
91
+ return _parse_response(response)
92
+
93
+ def _url(self, path: str) -> str:
94
+ # A caller-supplied httpx client may not carry our base URL.
95
+ return path if str(self._client.base_url) else self._base_url + path
96
+
97
+ def get(self, path: str, *, params: Optional[Dict[str, Any]] = None) -> Any:
98
+ return self.request("GET", path, params=params)
99
+
100
+ def post(self, path: str, *, json: Optional[Any] = None, params: Optional[Dict[str, Any]] = None) -> Any:
101
+ return self.request("POST", path, json=json, params=params)
102
+
103
+ def put(self, path: str, *, json: Optional[Any] = None, params: Optional[Dict[str, Any]] = None) -> Any:
104
+ return self.request("PUT", path, json=json, params=params)
105
+
106
+ def patch(self, path: str, *, json: Optional[Any] = None, params: Optional[Dict[str, Any]] = None) -> Any:
107
+ return self.request("PATCH", path, json=json, params=params)
108
+
109
+ def delete(self, path: str, *, params: Optional[Dict[str, Any]] = None) -> Any:
110
+ return self.request("DELETE", path, params=params)
111
+
112
+ def close(self) -> None:
113
+ if self._owns_client:
114
+ self._client.close()
115
+
116
+
117
+ class AsyncHTTPClient:
118
+ """Asynchronous HTTP client."""
119
+
120
+ def __init__(
121
+ self,
122
+ *,
123
+ api_key: str,
124
+ base_url: str = _DEFAULT_BASE_URL,
125
+ timeout: float = _DEFAULT_TIMEOUT,
126
+ http_client: Optional[httpx.AsyncClient] = None,
127
+ ) -> None:
128
+ self._api_key = api_key
129
+ self._base_url = base_url.rstrip("/")
130
+ self._timeout = timeout
131
+ self._headers = _build_headers(api_key)
132
+ self._owns_client = http_client is None
133
+ self._client = http_client or httpx.AsyncClient(
134
+ base_url=self._base_url,
135
+ headers=self._headers,
136
+ timeout=timeout,
137
+ )
138
+
139
+ async def request(
140
+ self,
141
+ method: str,
142
+ path: str,
143
+ *,
144
+ json: Optional[Any] = None,
145
+ params: Optional[Dict[str, Any]] = None,
146
+ ) -> Any:
147
+ try:
148
+ response = await self._client.request(
149
+ method,
150
+ self._url(path),
151
+ json=json,
152
+ params=_clean_params(params),
153
+ headers=self._headers,
154
+ )
155
+ except httpx.ConnectError as exc:
156
+ raise ConnectionError(f"Failed to connect to {self._base_url}: {exc}") from exc
157
+ except httpx.TimeoutException as exc:
158
+ raise TimeoutError(f"Request to {path} timed out after {self._timeout}s") from exc
159
+
160
+ return _parse_response(response)
161
+
162
+ def _url(self, path: str) -> str:
163
+ # A caller-supplied httpx client may not carry our base URL.
164
+ return path if str(self._client.base_url) else self._base_url + path
165
+
166
+ async def get(self, path: str, *, params: Optional[Dict[str, Any]] = None) -> Any:
167
+ return await self.request("GET", path, params=params)
168
+
169
+ async def post(self, path: str, *, json: Optional[Any] = None, params: Optional[Dict[str, Any]] = None) -> Any:
170
+ return await self.request("POST", path, json=json, params=params)
171
+
172
+ async def put(self, path: str, *, json: Optional[Any] = None, params: Optional[Dict[str, Any]] = None) -> Any:
173
+ return await self.request("PUT", path, json=json, params=params)
174
+
175
+ async def patch(self, path: str, *, json: Optional[Any] = None, params: Optional[Dict[str, Any]] = None) -> Any:
176
+ return await self.request("PATCH", path, json=json, params=params)
177
+
178
+ async def delete(self, path: str, *, params: Optional[Dict[str, Any]] = None) -> Any:
179
+ return await self.request("DELETE", path, params=params)
180
+
181
+ async def close(self) -> None:
182
+ if self._owns_client:
183
+ await self._client.aclose()
184
+
185
+
186
+ def _clean_params(params: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
187
+ """Remove None values from query parameters and stringify the rest."""
188
+ if params is None:
189
+ return None
190
+ return {k: _param_value(v) for k, v in params.items() if v is not None}
191
+
192
+
193
+ def _param_value(value: Any) -> str:
194
+ if isinstance(value, bool):
195
+ return "true" if value else "false"
196
+ if isinstance(value, datetime):
197
+ return value.isoformat()
198
+ if isinstance(value, enum.Enum):
199
+ return str(value.value)
200
+ return value if isinstance(value, str) else str(value)
201
+
202
+
203
+ def compact(**fields: Any) -> Dict[str, Any]:
204
+ """Build a JSON body from keyword arguments, dropping those left as ``None``."""
205
+ return {k: _jsonable(v) for k, v in fields.items() if v is not None}
206
+
207
+
208
+ def _jsonable(value: Any) -> Any:
209
+ if isinstance(value, BaseModel):
210
+ return value.model_dump(mode="json", by_alias=True, exclude_none=True)
211
+ if isinstance(value, UUID):
212
+ return str(value)
213
+ if isinstance(value, enum.Enum):
214
+ return value.value
215
+ if isinstance(value, list):
216
+ return [_jsonable(v) for v in value]
217
+ return value
deutero/client.py ADDED
@@ -0,0 +1,215 @@
1
+ """Deutero API client — synchronous and asynchronous."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from typing import Any, Dict, Optional
7
+
8
+ import httpx
9
+
10
+ from deutero._http import _DEFAULT_BASE_URL, _DEFAULT_TIMEOUT, AsyncHTTPClient, SyncHTTPClient
11
+ from deutero.resources.analysis import Analysis, AsyncAnalysis
12
+ from deutero.resources.characteristics import AsyncCharacteristics, Characteristics
13
+ from deutero.resources.credits import AsyncCredits, Credits
14
+ from deutero.resources.embed import AsyncEmbed, Embed
15
+ from deutero.resources.graph import AsyncGraph, Graph
16
+ from deutero.resources.interviews import AsyncInterviews, Interviews
17
+ from deutero.resources.personas import AsyncPersonas, Personas
18
+ from deutero.resources.projects import AsyncProjects, Projects
19
+ from deutero.resources.questions import AsyncQuestions, Questions
20
+ from deutero.resources.recruitment import AsyncRecruitment, Recruitment
21
+ from deutero.resources.screening import AsyncScreening, Screening
22
+ from deutero.resources.simulations import AsyncSimulations, Simulations
23
+ from deutero.resources.studies import AsyncStudies, Studies
24
+ from deutero.resources.transcripts import AsyncTranscripts, Transcripts
25
+ from deutero.resources.webhooks import AsyncWebhooks, Webhooks
26
+ from deutero.resources.welcome import AsyncWelcome, Welcome
27
+
28
+
29
+ def _resolve_api_key(api_key: Optional[str]) -> str:
30
+ resolved_key = api_key or os.environ.get("DEUTERO_API_KEY", "")
31
+ if not resolved_key:
32
+ raise ValueError(
33
+ "No API key provided. Pass api_key= or set the DEUTERO_API_KEY environment variable."
34
+ )
35
+ return resolved_key
36
+
37
+
38
+ class Deutero:
39
+ """Synchronous client for the Deutero Study Management API.
40
+
41
+ Usage::
42
+
43
+ from deutero import Deutero
44
+
45
+ client = Deutero(api_key="your-api-key")
46
+
47
+ project = client.projects.create(name="Onboarding research")
48
+ study = client.studies.create(project_id=project.id, name="Why new users drop off")
49
+
50
+ The API key can also be set via the ``DEUTERO_API_KEY`` environment variable.
51
+
52
+ Args:
53
+ api_key: Your Deutero API key. Falls back to ``DEUTERO_API_KEY`` env var.
54
+ base_url: Override the base URL (default: ``https://dashboard.deutero.ai/study-api``).
55
+ timeout: Request timeout in seconds (default: 120).
56
+ http_client: Optional pre-configured :class:`httpx.Client`.
57
+ """
58
+
59
+ projects: Projects
60
+ studies: Studies
61
+ welcome: Welcome
62
+ screening: Screening
63
+ characteristics: Characteristics
64
+ questions: Questions
65
+ graph: Graph
66
+ recruitment: Recruitment
67
+ embed: Embed
68
+ personas: Personas
69
+ simulations: Simulations
70
+ interviews: Interviews
71
+ transcripts: Transcripts
72
+ analysis: Analysis
73
+ webhooks: Webhooks
74
+ credits: Credits
75
+
76
+ def __init__(
77
+ self,
78
+ *,
79
+ api_key: Optional[str] = None,
80
+ base_url: Optional[str] = None,
81
+ timeout: Optional[float] = None,
82
+ http_client: Optional[httpx.Client] = None,
83
+ ) -> None:
84
+ self._http = SyncHTTPClient(
85
+ api_key=_resolve_api_key(api_key),
86
+ base_url=base_url or _DEFAULT_BASE_URL,
87
+ timeout=timeout or _DEFAULT_TIMEOUT,
88
+ http_client=http_client,
89
+ )
90
+
91
+ self.projects = Projects(self._http)
92
+ self.studies = Studies(self._http)
93
+ self.welcome = Welcome(self._http)
94
+ self.screening = Screening(self._http)
95
+ self.characteristics = Characteristics(self._http)
96
+ self.questions = Questions(self._http)
97
+ self.graph = Graph(self._http)
98
+ self.recruitment = Recruitment(self._http)
99
+ self.embed = Embed(self._http)
100
+ self.personas = Personas(self._http)
101
+ self.simulations = Simulations(self._http)
102
+ self.interviews = Interviews(self._http)
103
+ self.transcripts = Transcripts(self._http)
104
+ self.analysis = Analysis(self._http)
105
+ self.webhooks = Webhooks(self._http)
106
+ self.credits = Credits(self._http)
107
+
108
+ def health(self) -> Dict[str, Any]:
109
+ """Liveness check. Does not require a valid API key."""
110
+ data: Dict[str, Any] = self._http.get("/health")
111
+ return data
112
+
113
+ def close(self) -> None:
114
+ """Close the underlying HTTP connection pool."""
115
+ self._http.close()
116
+
117
+ def __enter__(self) -> Deutero:
118
+ return self
119
+
120
+ def __exit__(self, *args: object) -> None:
121
+ self.close()
122
+
123
+ def __repr__(self) -> str:
124
+ return f"Deutero(base_url={self._http._base_url!r})"
125
+
126
+
127
+ class AsyncDeutero:
128
+ """Asynchronous client for the Deutero Study Management API.
129
+
130
+ Usage::
131
+
132
+ import asyncio
133
+ from deutero import AsyncDeutero
134
+
135
+ async def main():
136
+ async with AsyncDeutero(api_key="your-api-key") as client:
137
+ projects = await client.projects.list()
138
+
139
+ asyncio.run(main())
140
+
141
+ The API key can also be set via the ``DEUTERO_API_KEY`` environment variable.
142
+
143
+ Args:
144
+ api_key: Your Deutero API key. Falls back to ``DEUTERO_API_KEY`` env var.
145
+ base_url: Override the base URL (default: ``https://dashboard.deutero.ai/study-api``).
146
+ timeout: Request timeout in seconds (default: 120).
147
+ http_client: Optional pre-configured :class:`httpx.AsyncClient`.
148
+ """
149
+
150
+ projects: AsyncProjects
151
+ studies: AsyncStudies
152
+ welcome: AsyncWelcome
153
+ screening: AsyncScreening
154
+ characteristics: AsyncCharacteristics
155
+ questions: AsyncQuestions
156
+ graph: AsyncGraph
157
+ recruitment: AsyncRecruitment
158
+ embed: AsyncEmbed
159
+ personas: AsyncPersonas
160
+ simulations: AsyncSimulations
161
+ interviews: AsyncInterviews
162
+ transcripts: AsyncTranscripts
163
+ analysis: AsyncAnalysis
164
+ webhooks: AsyncWebhooks
165
+ credits: AsyncCredits
166
+
167
+ def __init__(
168
+ self,
169
+ *,
170
+ api_key: Optional[str] = None,
171
+ base_url: Optional[str] = None,
172
+ timeout: Optional[float] = None,
173
+ http_client: Optional[httpx.AsyncClient] = None,
174
+ ) -> None:
175
+ self._http = AsyncHTTPClient(
176
+ api_key=_resolve_api_key(api_key),
177
+ base_url=base_url or _DEFAULT_BASE_URL,
178
+ timeout=timeout or _DEFAULT_TIMEOUT,
179
+ http_client=http_client,
180
+ )
181
+
182
+ self.projects = AsyncProjects(self._http)
183
+ self.studies = AsyncStudies(self._http)
184
+ self.welcome = AsyncWelcome(self._http)
185
+ self.screening = AsyncScreening(self._http)
186
+ self.characteristics = AsyncCharacteristics(self._http)
187
+ self.questions = AsyncQuestions(self._http)
188
+ self.graph = AsyncGraph(self._http)
189
+ self.recruitment = AsyncRecruitment(self._http)
190
+ self.embed = AsyncEmbed(self._http)
191
+ self.personas = AsyncPersonas(self._http)
192
+ self.simulations = AsyncSimulations(self._http)
193
+ self.interviews = AsyncInterviews(self._http)
194
+ self.transcripts = AsyncTranscripts(self._http)
195
+ self.analysis = AsyncAnalysis(self._http)
196
+ self.webhooks = AsyncWebhooks(self._http)
197
+ self.credits = AsyncCredits(self._http)
198
+
199
+ async def health(self) -> Dict[str, Any]:
200
+ """Liveness check. See :meth:`Deutero.health`."""
201
+ data: Dict[str, Any] = await self._http.get("/health")
202
+ return data
203
+
204
+ async def close(self) -> None:
205
+ """Close the underlying HTTP connection pool."""
206
+ await self._http.close()
207
+
208
+ async def __aenter__(self) -> AsyncDeutero:
209
+ return self
210
+
211
+ async def __aexit__(self, *args: object) -> None:
212
+ await self.close()
213
+
214
+ def __repr__(self) -> str:
215
+ return f"AsyncDeutero(base_url={self._http._base_url!r})"