curious-pyapi 0.1.7__tar.gz → 0.1.9__tar.gz
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.
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/CHANGELOG.md +12 -0
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/PKG-INFO +1 -1
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/api/curious.py +6 -6
- curious_pyapi-0.1.7/S0-CuriousAccountCreation.ipynb +0 -287
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/.github/dependabot.yaml +0 -0
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/.github/workflows/publish_to_pypi.yaml +0 -0
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/.gitignore +0 -0
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/.markdownlint.yaml +0 -0
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/.pre-commit-config.yaml +0 -0
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/.python-version +0 -0
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/.vscode/settings.json +0 -0
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/LICENSE +0 -0
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/README.md +0 -0
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/pyproject.toml +0 -0
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/__init__.py +0 -0
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/api/__init__.py +0 -0
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/exceptions/__init__.py +0 -0
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/exceptions/exceptions.py +0 -0
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/py.typed +0 -0
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/schema/__init__.py +0 -0
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/schema/curious.py +0 -0
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/schema/pyapi.py +0 -0
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/schema/regex.py +0 -0
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/utils/__init__.py +0 -0
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/utils/data.py +0 -0
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/utils/defaults.py +0 -0
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/utils/logging.py +0 -0
- {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/uv.lock +0 -0
|
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.9]
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Retry up to 3 times by default on network error.
|
|
13
|
+
|
|
14
|
+
## [0.1.8]
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Forward reference for `CuriousApiClient.__enter__`'s return type.
|
|
19
|
+
|
|
8
20
|
## [0.1.7]
|
|
9
21
|
|
|
10
22
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: curious-pyapi
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
4
4
|
Summary: Python interface for Curious API.
|
|
5
5
|
Project-URL: Repository, https://github.com/childmindresearch/curious-pyapi
|
|
6
6
|
Project-URL: Issues, https://github.com/childmindresearch/curious-pyapi/issues
|
|
@@ -155,7 +155,7 @@ class CuriousApiClient(ApiClient):
|
|
|
155
155
|
raise AuthenticationError(msg) from e
|
|
156
156
|
|
|
157
157
|
def request(
|
|
158
|
-
self, method: str, path: str | httpx.URL, **kwargs: Any
|
|
158
|
+
self, method: str, path: str | httpx.URL, *, _retries: int = 3, **kwargs: Any
|
|
159
159
|
) -> httpx.Response:
|
|
160
160
|
"""Centralized transport gateway with error mapping and auth checking."""
|
|
161
161
|
clean_path = str(path).lstrip("/")
|
|
@@ -164,19 +164,19 @@ class CuriousApiClient(ApiClient):
|
|
|
164
164
|
LOGGER.warning(
|
|
165
165
|
"Attempting unauthenticated '%s' request to '%s'", method, clean_path
|
|
166
166
|
)
|
|
167
|
-
request = self._client.build_request(method, clean_path, **kwargs)
|
|
168
|
-
LOGGER.exception(request)
|
|
169
167
|
try:
|
|
170
168
|
response = self._client.request(method, clean_path, **kwargs)
|
|
171
169
|
response.raise_for_status()
|
|
172
170
|
return response
|
|
173
171
|
except httpx.HTTPStatusError as e:
|
|
174
|
-
LOGGER.
|
|
175
|
-
LOGGER.
|
|
172
|
+
LOGGER.debug("Sent Headers: %s", dict(e.request.headers))
|
|
173
|
+
LOGGER.debug("Response Error Details: %s", e.response.text)
|
|
176
174
|
raise ApiStatusError(
|
|
177
175
|
str(e.response), request=e.request, response=e.response
|
|
178
176
|
) from e
|
|
179
177
|
except httpx.HTTPError as e:
|
|
178
|
+
if _retries > 0:
|
|
179
|
+
return self.request(method, clean_path, _retries=_retries - 1, **kwargs)
|
|
180
180
|
msg = f"Network transport error: {e}"
|
|
181
181
|
raise CuriousApiError(msg) from e
|
|
182
182
|
|
|
@@ -195,7 +195,7 @@ class CuriousApiClient(ApiClient):
|
|
|
195
195
|
"""For each row in DataFrame, create an account in Curious."""
|
|
196
196
|
_create_accounts_from_df(self, df, applet_id, user_type, limit=limit)
|
|
197
197
|
|
|
198
|
-
def __enter__(self) -> CuriousApiClient:
|
|
198
|
+
def __enter__(self) -> "CuriousApiClient":
|
|
199
199
|
"""Enter context manager and return self."""
|
|
200
200
|
return self
|
|
201
201
|
|
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"cells": [
|
|
3
|
-
{
|
|
4
|
-
"cell_type": "markdown",
|
|
5
|
-
"id": "f06b51dd-6552-4b22-91b9-e6d68880186c",
|
|
6
|
-
"metadata": {
|
|
7
|
-
"microsoft": {
|
|
8
|
-
"language": "python",
|
|
9
|
-
"language_group": "synapse_pyspark"
|
|
10
|
-
},
|
|
11
|
-
"nteract": {
|
|
12
|
-
"transient": {
|
|
13
|
-
"deleting": false
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
"source": [
|
|
18
|
-
"# Curious Account Creation\n",
|
|
19
|
-
"\n",
|
|
20
|
-
"1. Load file of IDs and passwords.\n",
|
|
21
|
-
"2. Invite each ID to applet.\n",
|
|
22
|
-
"3. Create an account for each ID using password from file.\n",
|
|
23
|
-
"4. As each account, log in and accept invitation."
|
|
24
|
-
]
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
"cell_type": "code",
|
|
28
|
-
"execution_count": null,
|
|
29
|
-
"id": "a7500f61-2e8c-4f08-ba11-6a2749fc4000",
|
|
30
|
-
"metadata": {
|
|
31
|
-
"microsoft": {
|
|
32
|
-
"language": "python",
|
|
33
|
-
"language_group": "jupyter_python"
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
"outputs": [],
|
|
37
|
-
"source": [
|
|
38
|
-
"from pathlib import Path\n",
|
|
39
|
-
"\n",
|
|
40
|
-
"from azure.identity import DefaultAzureCredential\n",
|
|
41
|
-
"from azure.keyvault.secrets import SecretClient\n",
|
|
42
|
-
"import httpx\n",
|
|
43
|
-
"import notebookutils\n",
|
|
44
|
-
"import polars as pl\n",
|
|
45
|
-
"\n",
|
|
46
|
-
"from curious_pyapi.api.curious import CuriousApiClient\n",
|
|
47
|
-
"from curious_pyapi.exceptions.exceptions import allow_existing\n",
|
|
48
|
-
"from curious_pyapi.schema.curious import CuriousId\n",
|
|
49
|
-
"from curious_pyapi.schema.pyapi import CuriousAuth\n",
|
|
50
|
-
"from curious_pyapi.utils.logging import get_logger\n",
|
|
51
|
-
"\n",
|
|
52
|
-
"conf = notebookutils.variableLibrary.getLibrary(\"ConfigurationVariables\")\n",
|
|
53
|
-
"\n",
|
|
54
|
-
"LOGGER = get_logger(__name__)\n",
|
|
55
|
-
"\n",
|
|
56
|
-
"APPLET_ID = conf.getVariable(\"AppletIdProlificMentalStateModelingStudy\")\n",
|
|
57
|
-
"EMAIL_FORMAT = \"msm+Z{}@childmind.org\"\n",
|
|
58
|
-
"TEMPORARY_NAME_FORMAT = \"_{}\"\n",
|
|
59
|
-
"\n",
|
|
60
|
-
"\n",
|
|
61
|
-
"def map_prolific_to_curious(df: pl.DataFrame, pl_namespace: str) -> pl.DataFrame:\n",
|
|
62
|
-
" \"\"\"Transform a username-password DataFrame into a CuriousAccount DataFrame.\"\"\"\n",
|
|
63
|
-
" record_id_str = pl.col(\"record_id\").cast(pl.String).str.zfill(6)\n",
|
|
64
|
-
" match pl_namespace:\n",
|
|
65
|
-
" case \"new_curious_user\":\n",
|
|
66
|
-
" _name_expr = pl.format(TEMPORARY_NAME_FORMAT, record_id_str)\n",
|
|
67
|
-
" case _:\n",
|
|
68
|
-
" _name_expr = pl.lit(\"\")\n",
|
|
69
|
-
"\n",
|
|
70
|
-
" return getattr(\n",
|
|
71
|
-
" df.with_columns(\n",
|
|
72
|
-
" email=pl.format(EMAIL_FORMAT, record_id_str),\n",
|
|
73
|
-
" password=pl.col(\"password\").cast(pl.String),\n",
|
|
74
|
-
" firstName=_name_expr,\n",
|
|
75
|
-
" lastName=_name_expr,\n",
|
|
76
|
-
" language=pl.lit(\"en\"),\n",
|
|
77
|
-
" secretUserId=pl.col(\"record_id\").cast(pl.String),\n",
|
|
78
|
-
" nickname=pl.col(\"record_id\").cast(pl.String),\n",
|
|
79
|
-
" tag=pl.lit(\"Prolific\"),\n",
|
|
80
|
-
" ),\n",
|
|
81
|
-
" pl_namespace,\n",
|
|
82
|
-
" ).enforce_schema()\n",
|
|
83
|
-
"\n",
|
|
84
|
-
"\n",
|
|
85
|
-
"def create_accounts_from_df(\n",
|
|
86
|
-
" client: CuriousApiClient,\n",
|
|
87
|
-
" df: pl.DataFrame,\n",
|
|
88
|
-
" applet_id: CuriousId,\n",
|
|
89
|
-
" user_type: str = \"respondent\",\n",
|
|
90
|
-
" *,\n",
|
|
91
|
-
" limit=None,\n",
|
|
92
|
-
") -> None:\n",
|
|
93
|
-
" \"\"\"For each row in DataFrame, create an account in Curious.\"\"\"\n",
|
|
94
|
-
" invitations: dict[str, CuriousId] = {}\n",
|
|
95
|
-
" api_df = map_prolific_to_curious(df, \"curious_account\")\n",
|
|
96
|
-
" if limit:\n",
|
|
97
|
-
" api_df = api_df.head(limit)\n",
|
|
98
|
-
" for record in api_df.to_dicts():\n",
|
|
99
|
-
" invitation = allow_existing(\n",
|
|
100
|
-
" client.invite_user(applet_id=applet_id, user_type=user_type).post,\n",
|
|
101
|
-
" json=record,\n",
|
|
102
|
-
" )\n",
|
|
103
|
-
" if invitation:\n",
|
|
104
|
-
" invitation = invitation.json().get(\"result\")\n",
|
|
105
|
-
" if not invitation:\n",
|
|
106
|
-
" all_invitations = (\n",
|
|
107
|
-
" client.invitations()\n",
|
|
108
|
-
" .get(params={\"appletId\": applet_id, \"userType\": \"respondent\"})\n",
|
|
109
|
-
" .json()\n",
|
|
110
|
-
" .get(\"result\", [])\n",
|
|
111
|
-
" )\n",
|
|
112
|
-
" this_user_invitations = [\n",
|
|
113
|
-
" i\n",
|
|
114
|
-
" for i in all_invitations\n",
|
|
115
|
-
" if all(\n",
|
|
116
|
-
" [\n",
|
|
117
|
-
" i.get(\"appletId\") == applet_id,\n",
|
|
118
|
-
" i.get(\"secretUserId\") == record[\"secretUserId\"],\n",
|
|
119
|
-
" ]\n",
|
|
120
|
-
" )\n",
|
|
121
|
-
" ]\n",
|
|
122
|
-
" if this_user_invitations:\n",
|
|
123
|
-
" invitation = this_user_invitations[0]\n",
|
|
124
|
-
" if invitation and \"key\" in invitation:\n",
|
|
125
|
-
" invitations[record[\"secretUserId\"]] = invitation[\"key\"]\n",
|
|
126
|
-
" new_user_df = map_prolific_to_curious(df, \"new_curious_user\")\n",
|
|
127
|
-
" for user in new_user_df.head(limit).to_dicts():\n",
|
|
128
|
-
" response = allow_existing(client.user_create.post, json=user)\n",
|
|
129
|
-
" secret_id = user[\"firstName\"][1:]\n",
|
|
130
|
-
" if secret_id in invitations:\n",
|
|
131
|
-
" user_client = CuriousApiClient(\n",
|
|
132
|
-
" auth=CuriousAuth(\n",
|
|
133
|
-
" curious_email=user[\"email\"], curious_password=user[\"password\"]\n",
|
|
134
|
-
" )\n",
|
|
135
|
-
" )\n",
|
|
136
|
-
" response = user_client.invitation_accept(key=invitations[secret_id]).post()\n",
|
|
137
|
-
" if not httpx.codes.is_success(response.status_code):\n",
|
|
138
|
-
" response.raise_for_status()\n",
|
|
139
|
-
" del user_client"
|
|
140
|
-
]
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
"cell_type": "code",
|
|
144
|
-
"execution_count": null,
|
|
145
|
-
"id": "23eee694-1869-4893-9afe-6ef54c259090",
|
|
146
|
-
"metadata": {
|
|
147
|
-
"microsoft": {
|
|
148
|
-
"language": "python",
|
|
149
|
-
"language_group": "jupyter_python"
|
|
150
|
-
}
|
|
151
|
-
},
|
|
152
|
-
"outputs": [],
|
|
153
|
-
"source": [
|
|
154
|
-
"secret_client = SecretClient(\n",
|
|
155
|
-
" vault_url=conf.getVariable(\"KeyVaultUri\"),\n",
|
|
156
|
-
" credential=DefaultAzureCredential(),\n",
|
|
157
|
-
")\n",
|
|
158
|
-
"admin_credentials = CuriousAuth(\n",
|
|
159
|
-
" curious_email=secret_client.get_secret(\"CuriousEmail\").value,\n",
|
|
160
|
-
" curious_password=secret_client.get_secret(\"CuriousPassword\").value,\n",
|
|
161
|
-
" applet_id=APPLET_ID,\n",
|
|
162
|
-
" applet_password=secret_client.get_secret(\"MsmProlificAppletPassword\").value,\n",
|
|
163
|
-
")"
|
|
164
|
-
]
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
"cell_type": "markdown",
|
|
168
|
-
"id": "426252e6-31f2-4091-b6a7-7fe5d7fa3c37",
|
|
169
|
-
"metadata": {
|
|
170
|
-
"microsoft": {
|
|
171
|
-
"language": "python",
|
|
172
|
-
"language_group": "synapse_pyspark"
|
|
173
|
-
},
|
|
174
|
-
"nteract": {
|
|
175
|
-
"transient": {
|
|
176
|
-
"deleting": false
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
},
|
|
180
|
-
"source": [
|
|
181
|
-
"## 1. Load file of IDs and passwords"
|
|
182
|
-
]
|
|
183
|
-
},
|
|
184
|
-
{
|
|
185
|
-
"cell_type": "code",
|
|
186
|
-
"execution_count": null,
|
|
187
|
-
"id": "1a55d086-9f84-491d-aeb5-d997df3557a9",
|
|
188
|
-
"metadata": {
|
|
189
|
-
"microsoft": {
|
|
190
|
-
"language": "python",
|
|
191
|
-
"language_group": "jupyter_python"
|
|
192
|
-
}
|
|
193
|
-
},
|
|
194
|
-
"outputs": [],
|
|
195
|
-
"source": [
|
|
196
|
-
"df = pl.read_excel(Path(\"Files/study_passwords.xlsx\"))\n",
|
|
197
|
-
"\"\"\"Raw DataFrame of IDs and passwords.\"\"\""
|
|
198
|
-
]
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
"cell_type": "markdown",
|
|
202
|
-
"id": "0f3b308c-dabc-42fa-9d6e-b11494dcc59a",
|
|
203
|
-
"metadata": {
|
|
204
|
-
"microsoft": {
|
|
205
|
-
"language": "python",
|
|
206
|
-
"language_group": "jupyter_python"
|
|
207
|
-
},
|
|
208
|
-
"nteract": {
|
|
209
|
-
"transient": {
|
|
210
|
-
"deleting": false
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
},
|
|
214
|
-
"source": [
|
|
215
|
-
"## 2‒4\n",
|
|
216
|
-
"### 2. Invite each ID to applet\n",
|
|
217
|
-
"### 3. Create an account for each ID using password from file.\n",
|
|
218
|
-
"### 4. As each account, log in and accept invitation."
|
|
219
|
-
]
|
|
220
|
-
},
|
|
221
|
-
{
|
|
222
|
-
"cell_type": "code",
|
|
223
|
-
"execution_count": null,
|
|
224
|
-
"id": "73f61305-9847-493f-af33-04787d3afecb",
|
|
225
|
-
"metadata": {
|
|
226
|
-
"microsoft": {
|
|
227
|
-
"language": "python",
|
|
228
|
-
"language_group": "jupyter_python"
|
|
229
|
-
}
|
|
230
|
-
},
|
|
231
|
-
"outputs": [],
|
|
232
|
-
"source": [
|
|
233
|
-
"admin_client = CuriousApiClient(auth=admin_credentials)\n",
|
|
234
|
-
"create_accounts_from_df(admin_client, df, applet_id=APPLET_ID)"
|
|
235
|
-
]
|
|
236
|
-
}
|
|
237
|
-
],
|
|
238
|
-
"metadata": {
|
|
239
|
-
"dependencies": {
|
|
240
|
-
"environment": {
|
|
241
|
-
"environmentId": "e11945f7-786a-451b-aeee-5ddebde287a4",
|
|
242
|
-
"workspaceId": "ea346909-a97e-43b1-b6ad-0670fd81ece4"
|
|
243
|
-
},
|
|
244
|
-
"lakehouse": {
|
|
245
|
-
"default_lakehouse": "cbe8c20b-e1f4-4b58-a6fa-f40541aade33",
|
|
246
|
-
"default_lakehouse_name": "MsmDevBronze",
|
|
247
|
-
"default_lakehouse_workspace_id": "879f45c2-2a52-471e-ab5b-abf825258afb",
|
|
248
|
-
"known_lakehouses": [
|
|
249
|
-
{
|
|
250
|
-
"id": "cbe8c20b-e1f4-4b58-a6fa-f40541aade33"
|
|
251
|
-
}
|
|
252
|
-
]
|
|
253
|
-
}
|
|
254
|
-
},
|
|
255
|
-
"kernel_info": {
|
|
256
|
-
"jupyter_kernel_name": "python3.12",
|
|
257
|
-
"name": "jupyter"
|
|
258
|
-
},
|
|
259
|
-
"kernelspec": {
|
|
260
|
-
"display_name": "Jupyter",
|
|
261
|
-
"name": "jupyter"
|
|
262
|
-
},
|
|
263
|
-
"language_info": {
|
|
264
|
-
"name": "python"
|
|
265
|
-
},
|
|
266
|
-
"microsoft": {
|
|
267
|
-
"language": "python",
|
|
268
|
-
"language_group": "jupyter_python",
|
|
269
|
-
"ms_spell_check": {
|
|
270
|
-
"ms_spell_check_language": "en"
|
|
271
|
-
}
|
|
272
|
-
},
|
|
273
|
-
"nteract": {
|
|
274
|
-
"version": "nteract-front-end@1.0.0"
|
|
275
|
-
},
|
|
276
|
-
"spark_compute": {
|
|
277
|
-
"compute_id": "/trident/default",
|
|
278
|
-
"session_options": {
|
|
279
|
-
"conf": {
|
|
280
|
-
"spark.synapse.nbs.session.timeout": "1200000"
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
},
|
|
285
|
-
"nbformat": 4,
|
|
286
|
-
"nbformat_minor": 5
|
|
287
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|