curious-pyapi 0.1.5__tar.gz → 0.1.7__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.5 → curious_pyapi-0.1.7}/CHANGELOG.md +1 -1
  2. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/PKG-INFO +3 -3
  3. curious_pyapi-0.1.7/S0-CuriousAccountCreation.ipynb +287 -0
  4. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/pyproject.toml +3 -3
  5. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/uv.lock +2 -2
  6. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/.github/dependabot.yaml +0 -0
  7. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/.github/workflows/publish_to_pypi.yaml +0 -0
  8. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/.gitignore +0 -0
  9. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/.markdownlint.yaml +0 -0
  10. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/.pre-commit-config.yaml +0 -0
  11. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/.python-version +0 -0
  12. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/.vscode/settings.json +0 -0
  13. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/LICENSE +0 -0
  14. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/README.md +0 -0
  15. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/src/curious_pyapi/__init__.py +0 -0
  16. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/src/curious_pyapi/api/__init__.py +0 -0
  17. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/src/curious_pyapi/api/curious.py +0 -0
  18. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/src/curious_pyapi/exceptions/__init__.py +0 -0
  19. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/src/curious_pyapi/exceptions/exceptions.py +0 -0
  20. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/src/curious_pyapi/py.typed +0 -0
  21. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/src/curious_pyapi/schema/__init__.py +0 -0
  22. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/src/curious_pyapi/schema/curious.py +0 -0
  23. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/src/curious_pyapi/schema/pyapi.py +0 -0
  24. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/src/curious_pyapi/schema/regex.py +0 -0
  25. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/src/curious_pyapi/utils/__init__.py +0 -0
  26. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/src/curious_pyapi/utils/data.py +0 -0
  27. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/src/curious_pyapi/utils/defaults.py +0 -0
  28. {curious_pyapi-0.1.5 → curious_pyapi-0.1.7}/src/curious_pyapi/utils/logging.py +0 -0
@@ -5,7 +5,7 @@ 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.5]
8
+ ## [0.1.7]
9
9
 
10
10
  ### Added
11
11
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: curious-pyapi
3
- Version: 0.1.5
3
+ Version: 0.1.7
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
@@ -22,8 +22,8 @@ Classifier: Typing :: Typed
22
22
  Requires-Python: >=3.12
23
23
  Requires-Dist: fastexcel>=0.21.0
24
24
  Requires-Dist: httpx>=0.28.1
25
- Requires-Dist: polars>=1.44.0
26
- Requires-Dist: pydantic[email]>=2.13.4
25
+ Requires-Dist: polars>=1.44.1
26
+ Requires-Dist: pydantic[email]>=2.13.5
27
27
  Description-Content-Type: text/markdown
28
28
 
29
29
  # Curious-PyAPI
@@ -0,0 +1,287 @@
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
+ }
@@ -14,8 +14,8 @@ requires-python = ">=3.12"
14
14
  dependencies = [
15
15
  "fastexcel>=0.21.0",
16
16
  "httpx>=0.28.1",
17
- "polars>=1.44.0",
18
- "pydantic[email]>=2.13.4",
17
+ "polars>=1.44.1",
18
+ "pydantic[email]>=2.13.5",
19
19
  ]
20
20
  license-files = ["LICENSE"]
21
21
  license = "MIT"
@@ -46,7 +46,7 @@ reportUnknownMemberType = false
46
46
 
47
47
  [tool.ruff]
48
48
  line-length = 88
49
- target-version = "py314"
49
+ target-version = "py312"
50
50
 
51
51
  [tool.ruff.lint]
52
52
  extend-select = ["A", "B904", "C4", "D", "E", "EM", "F541", "G", "I", "ICN", "LOG007", "N", "NPY", "PL", "RET", "RSE", "RUF", "Q", "T20", "TRY002", "TRY201", "TRY400", "TRY401", "UP032", "W"]
@@ -47,8 +47,8 @@ dependencies = [
47
47
  requires-dist = [
48
48
  { name = "fastexcel", specifier = ">=0.21.0" },
49
49
  { name = "httpx", specifier = ">=0.28.1" },
50
- { name = "polars", specifier = ">=1.44.0" },
51
- { name = "pydantic", extras = ["email"], specifier = ">=2.13.4" },
50
+ { name = "polars", specifier = ">=1.44.1" },
51
+ { name = "pydantic", extras = ["email"], specifier = ">=2.13.5" },
52
52
  ]
53
53
 
54
54
  [[package]]
File without changes
File without changes
File without changes