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.
Files changed (28) hide show
  1. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/CHANGELOG.md +12 -0
  2. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/PKG-INFO +1 -1
  3. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/api/curious.py +6 -6
  4. curious_pyapi-0.1.7/S0-CuriousAccountCreation.ipynb +0 -287
  5. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/.github/dependabot.yaml +0 -0
  6. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/.github/workflows/publish_to_pypi.yaml +0 -0
  7. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/.gitignore +0 -0
  8. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/.markdownlint.yaml +0 -0
  9. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/.pre-commit-config.yaml +0 -0
  10. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/.python-version +0 -0
  11. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/.vscode/settings.json +0 -0
  12. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/LICENSE +0 -0
  13. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/README.md +0 -0
  14. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/pyproject.toml +0 -0
  15. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/__init__.py +0 -0
  16. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/api/__init__.py +0 -0
  17. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/exceptions/__init__.py +0 -0
  18. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/exceptions/exceptions.py +0 -0
  19. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/py.typed +0 -0
  20. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/schema/__init__.py +0 -0
  21. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/schema/curious.py +0 -0
  22. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/schema/pyapi.py +0 -0
  23. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/schema/regex.py +0 -0
  24. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/utils/__init__.py +0 -0
  25. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/utils/data.py +0 -0
  26. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/utils/defaults.py +0 -0
  27. {curious_pyapi-0.1.7 → curious_pyapi-0.1.9}/src/curious_pyapi/utils/logging.py +0 -0
  28. {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.7
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.exception("Sent Headers: %s", dict(e.request.headers))
175
- LOGGER.exception("Response Error Details: %s", e.response.text)
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