cognitive3dpy 1.0.0__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.
- cognitive3dpy-1.0.0/PKG-INFO +203 -0
- cognitive3dpy-1.0.0/README.md +188 -0
- cognitive3dpy-1.0.0/pyproject.toml +53 -0
- cognitive3dpy-1.0.0/src/cognitive3dpy/__init__.py +38 -0
- cognitive3dpy-1.0.0/src/cognitive3dpy/_client.py +139 -0
- cognitive3dpy-1.0.0/src/cognitive3dpy/_filters.py +134 -0
- cognitive3dpy-1.0.0/src/cognitive3dpy/_lookups.py +369 -0
- cognitive3dpy-1.0.0/src/cognitive3dpy/_pagination.py +120 -0
- cognitive3dpy-1.0.0/src/cognitive3dpy/_transform.py +326 -0
- cognitive3dpy-1.0.0/src/cognitive3dpy/auth.py +63 -0
- cognitive3dpy-1.0.0/src/cognitive3dpy/events.py +209 -0
- cognitive3dpy-1.0.0/src/cognitive3dpy/exitpoll.py +260 -0
- cognitive3dpy-1.0.0/src/cognitive3dpy/objectives.py +344 -0
- cognitive3dpy-1.0.0/src/cognitive3dpy/py.typed +0 -0
- cognitive3dpy-1.0.0/src/cognitive3dpy/session_objectives.py +308 -0
- cognitive3dpy-1.0.0/src/cognitive3dpy/sessions.py +201 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: cognitive3dpy
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python client for the Cognitive3D analytics API.
|
|
5
|
+
Author: Mona
|
|
6
|
+
Author-email: Mona <monajhzhu@gmail.com>
|
|
7
|
+
Requires-Dist: httpx>=0.28.1
|
|
8
|
+
Requires-Dist: polars>=1.38.1
|
|
9
|
+
Requires-Dist: python-dateutil>=2.9.0.post0
|
|
10
|
+
Requires-Dist: pandas>=3.0 ; extra == 'pandas'
|
|
11
|
+
Requires-Dist: pyarrow>=23.0.1 ; extra == 'pandas'
|
|
12
|
+
Requires-Python: >=3.11
|
|
13
|
+
Provides-Extra: pandas
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# cognitive3dpy
|
|
17
|
+
|
|
18
|
+
`cognitive3dpy` is a Python client for the [Cognitive3D](https://cognitive3d.com) analytics API. It turns raw JSON responses into a Polars or Panadas DataFrames ready for analysis, handling authentication, pagination, property flattening, name resolution, and type coercion so you can go from API key to analysis-ready data in a few lines of code.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install cognitive3dpy
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
To enable pandas output (`output="pandas"`), install with the optional extra:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install "cognitive3dpy[pandas]"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Getting an API Key
|
|
33
|
+
|
|
34
|
+
1. Log in to your [Cognitive3D Dashboard](https://app.cognitive3d.com)
|
|
35
|
+
2. Click your profile icon in the top-right corner and select **Settings**
|
|
36
|
+
3. Navigate to the **API Keys** tab
|
|
37
|
+
4. Click **Create API Key**, give it a name, and copy the generated key
|
|
38
|
+
|
|
39
|
+
Store the key in a `.env` file (never commit this to version control):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
C3D_API_KEY=your_api_key_here
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Or set it as an environment variable in your shell:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
export C3D_API_KEY=your_api_key_here
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
import cognitive3dpy as c3d
|
|
55
|
+
|
|
56
|
+
# Authenticate and set project
|
|
57
|
+
c3d.c3d_auth() # reads C3D_API_KEY env var, or pass key directly
|
|
58
|
+
c3d.c3d_project(4460) # set default project ID
|
|
59
|
+
|
|
60
|
+
# Pull data
|
|
61
|
+
sessions = c3d.c3d_sessions(n=100)
|
|
62
|
+
events = c3d.c3d_events()
|
|
63
|
+
results = c3d.c3d_objective_results(group_by="steps")
|
|
64
|
+
session_steps = c3d.c3d_session_objectives()
|
|
65
|
+
polls = c3d.c3d_exitpoll()
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Data Streams
|
|
69
|
+
|
|
70
|
+
| Function | Description | Key output columns |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| `c3d_sessions()` | Session-level metrics and properties | `session_id`, `duration_s`, `hmd`, `c3d_metrics_*`, `c3d_device_*`, `c3d_geo_*` |
|
|
73
|
+
| `c3d_sessions(session_type="scene")` | Sessions split by scene visited — one row per session-scene | Adds `scene_id`, `scene_version_id`, `scene_name` |
|
|
74
|
+
| `c3d_events()` | One row per in-session event with session context | `event_name`, `event_date`, `position_x/y/z`, `object`, `scene_name`, `prop_*` |
|
|
75
|
+
| `c3d_objective_results()` | Objective success/failure counts by version | `objective_name`, `version_number`, `succeeded`, `failed`, `completion_rate` |
|
|
76
|
+
| `c3d_objective_results(group_by="steps")` | Step-level detail for each objective | Adds `step_name`, `step_type`, `step_detail`, `avg_completion_time_s` |
|
|
77
|
+
| `c3d_session_objectives()` | Per-session objective step results — one row per step per session | `session_id`, `participant_id`, `objective_name`, `step_number`, `step_description`, `step_result`, `step_duration_sec` |
|
|
78
|
+
| `c3d_exitpoll()` | Exit poll survey responses | `question_title`, `value`, `value_label`, per hook/version |
|
|
79
|
+
|
|
80
|
+
## Sessions
|
|
81
|
+
|
|
82
|
+
Retrieve session-level data with optional date filtering and compact/full column modes.
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
# Last 30 days, compact columns (default)
|
|
86
|
+
sessions = c3d.c3d_sessions(n=100)
|
|
87
|
+
|
|
88
|
+
# Custom date range, all columns
|
|
89
|
+
sessions = c3d.c3d_sessions(
|
|
90
|
+
n=50,
|
|
91
|
+
start_date="2025-01-01",
|
|
92
|
+
end_date="2025-06-01",
|
|
93
|
+
compact=False,
|
|
94
|
+
)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Scene Sessions
|
|
98
|
+
|
|
99
|
+
Use `session_type="scene"` to get session data broken out by scene — one row per session-scene combination. This is useful for comparing metrics across scenes within the same session. Defaults to the latest version of each scene.
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
# All scenes, latest versions (default)
|
|
103
|
+
scene_sessions = c3d.c3d_sessions(session_type="scene")
|
|
104
|
+
|
|
105
|
+
# Filter to a specific scene
|
|
106
|
+
scene_sessions = c3d.c3d_sessions(
|
|
107
|
+
session_type="scene",
|
|
108
|
+
scene_id="de704574-b03f-424e-be87-4985f85ed2e8",
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Filter to a specific scene version
|
|
112
|
+
scene_sessions = c3d.c3d_sessions(
|
|
113
|
+
session_type="scene",
|
|
114
|
+
scene_version_id=7011,
|
|
115
|
+
)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Events
|
|
119
|
+
|
|
120
|
+
Retrieve per-event data with session context attached. Events are unnested from sessions — one row per event. Dynamic object IDs are resolved to friendly names, and scene version IDs are resolved to scene names.
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
events = c3d.c3d_events(
|
|
124
|
+
start_date="2025-01-01",
|
|
125
|
+
n=20,
|
|
126
|
+
)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Objective Results
|
|
130
|
+
|
|
131
|
+
Query objective success/failure counts, optionally sliced by version or with step-level detail.
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
# By version (default)
|
|
135
|
+
results = c3d.c3d_objective_results(group_by="version")
|
|
136
|
+
|
|
137
|
+
# Step-level detail
|
|
138
|
+
detailed = c3d.c3d_objective_results(group_by="steps")
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Session Objectives
|
|
142
|
+
|
|
143
|
+
Retrieve per-session objective step results. Returns one row per step per session, with step descriptions and outcomes.
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
session_steps = c3d.c3d_session_objectives(
|
|
147
|
+
start_date="2025-01-01",
|
|
148
|
+
end_date="2025-06-01",
|
|
149
|
+
)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Exit Polls
|
|
153
|
+
|
|
154
|
+
Retrieve exit poll response counts across all hooks and versions. Returns one row per response option per question per version, with human-readable value labels.
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
# All hooks and versions
|
|
158
|
+
polls = c3d.c3d_exitpoll()
|
|
159
|
+
|
|
160
|
+
# Filter to a specific hook and version
|
|
161
|
+
polls = c3d.c3d_exitpoll(hook="end_questions", version=3)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Configuration
|
|
165
|
+
|
|
166
|
+
### Timeout
|
|
167
|
+
|
|
168
|
+
The default request timeout is 30 seconds per API call. To increase it:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
import cognitive3dpy as c3d
|
|
172
|
+
|
|
173
|
+
c3d.c3d_set_timeout(60) # 60 seconds
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Or set the `C3D_TIMEOUT` environment variable before your session starts:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
export C3D_TIMEOUT=60
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`c3d_set_timeout()` takes effect immediately. The environment variable is read once on first import, so it must be set before importing the package.
|
|
183
|
+
|
|
184
|
+
## Key Features
|
|
185
|
+
|
|
186
|
+
- **Compact mode** — `c3d_sessions(compact=True)` (default) returns ~40 curated columns; `compact=False` returns everything
|
|
187
|
+
- **Scene sessions** — `session_type="scene"` queries the latest version of each scene by default, giving the full project picture split by scene
|
|
188
|
+
- **Automatic name resolution** — dynamic object SDK IDs are resolved to friendly names in events and objective steps; scene version IDs are resolved to scene names
|
|
189
|
+
- **Date defaults** — all functions default to the last 30 days when no date range is specified
|
|
190
|
+
- **Column naming** — top-level API fields use `snake_case`; Cognitive3D properties retain their `c3d_` prefix (e.g., `c3d_metrics_fps_score`)
|
|
191
|
+
- **Polars-native** — returns `polars.DataFrame` by default; pass `output="pandas"` for a pandas DataFrame
|
|
192
|
+
- **Session filtering** — `exclude_test`, `exclude_idle`, `min_duration` parameters across functions
|
|
193
|
+
|
|
194
|
+
## Common Options
|
|
195
|
+
|
|
196
|
+
All data-fetching functions support these session filters:
|
|
197
|
+
|
|
198
|
+
- `exclude_test` / `exclude_idle` — filter out test and junk sessions (default `True`)
|
|
199
|
+
- `start_date` / `end_date` — date range as a `date`/`datetime` object, epoch timestamp, or `"YYYY-MM-DD"` string
|
|
200
|
+
- `min_duration` — minimum session duration in seconds
|
|
201
|
+
- `project_id` — override the default set by `c3d_project()`
|
|
202
|
+
- `output` — `"polars"` (default) or `"pandas"`
|
|
203
|
+
- `warn_empty` — emit a `UserWarning` when 0 rows are returned (default `True`); set to `False` to suppress
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# cognitive3dpy
|
|
2
|
+
|
|
3
|
+
`cognitive3dpy` is a Python client for the [Cognitive3D](https://cognitive3d.com) analytics API. It turns raw JSON responses into a Polars or Panadas DataFrames ready for analysis, handling authentication, pagination, property flattening, name resolution, and type coercion so you can go from API key to analysis-ready data in a few lines of code.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install cognitive3dpy
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
To enable pandas output (`output="pandas"`), install with the optional extra:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install "cognitive3dpy[pandas]"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Getting an API Key
|
|
18
|
+
|
|
19
|
+
1. Log in to your [Cognitive3D Dashboard](https://app.cognitive3d.com)
|
|
20
|
+
2. Click your profile icon in the top-right corner and select **Settings**
|
|
21
|
+
3. Navigate to the **API Keys** tab
|
|
22
|
+
4. Click **Create API Key**, give it a name, and copy the generated key
|
|
23
|
+
|
|
24
|
+
Store the key in a `.env` file (never commit this to version control):
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
C3D_API_KEY=your_api_key_here
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Or set it as an environment variable in your shell:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
export C3D_API_KEY=your_api_key_here
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import cognitive3dpy as c3d
|
|
40
|
+
|
|
41
|
+
# Authenticate and set project
|
|
42
|
+
c3d.c3d_auth() # reads C3D_API_KEY env var, or pass key directly
|
|
43
|
+
c3d.c3d_project(4460) # set default project ID
|
|
44
|
+
|
|
45
|
+
# Pull data
|
|
46
|
+
sessions = c3d.c3d_sessions(n=100)
|
|
47
|
+
events = c3d.c3d_events()
|
|
48
|
+
results = c3d.c3d_objective_results(group_by="steps")
|
|
49
|
+
session_steps = c3d.c3d_session_objectives()
|
|
50
|
+
polls = c3d.c3d_exitpoll()
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Data Streams
|
|
54
|
+
|
|
55
|
+
| Function | Description | Key output columns |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| `c3d_sessions()` | Session-level metrics and properties | `session_id`, `duration_s`, `hmd`, `c3d_metrics_*`, `c3d_device_*`, `c3d_geo_*` |
|
|
58
|
+
| `c3d_sessions(session_type="scene")` | Sessions split by scene visited — one row per session-scene | Adds `scene_id`, `scene_version_id`, `scene_name` |
|
|
59
|
+
| `c3d_events()` | One row per in-session event with session context | `event_name`, `event_date`, `position_x/y/z`, `object`, `scene_name`, `prop_*` |
|
|
60
|
+
| `c3d_objective_results()` | Objective success/failure counts by version | `objective_name`, `version_number`, `succeeded`, `failed`, `completion_rate` |
|
|
61
|
+
| `c3d_objective_results(group_by="steps")` | Step-level detail for each objective | Adds `step_name`, `step_type`, `step_detail`, `avg_completion_time_s` |
|
|
62
|
+
| `c3d_session_objectives()` | Per-session objective step results — one row per step per session | `session_id`, `participant_id`, `objective_name`, `step_number`, `step_description`, `step_result`, `step_duration_sec` |
|
|
63
|
+
| `c3d_exitpoll()` | Exit poll survey responses | `question_title`, `value`, `value_label`, per hook/version |
|
|
64
|
+
|
|
65
|
+
## Sessions
|
|
66
|
+
|
|
67
|
+
Retrieve session-level data with optional date filtering and compact/full column modes.
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
# Last 30 days, compact columns (default)
|
|
71
|
+
sessions = c3d.c3d_sessions(n=100)
|
|
72
|
+
|
|
73
|
+
# Custom date range, all columns
|
|
74
|
+
sessions = c3d.c3d_sessions(
|
|
75
|
+
n=50,
|
|
76
|
+
start_date="2025-01-01",
|
|
77
|
+
end_date="2025-06-01",
|
|
78
|
+
compact=False,
|
|
79
|
+
)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Scene Sessions
|
|
83
|
+
|
|
84
|
+
Use `session_type="scene"` to get session data broken out by scene — one row per session-scene combination. This is useful for comparing metrics across scenes within the same session. Defaults to the latest version of each scene.
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
# All scenes, latest versions (default)
|
|
88
|
+
scene_sessions = c3d.c3d_sessions(session_type="scene")
|
|
89
|
+
|
|
90
|
+
# Filter to a specific scene
|
|
91
|
+
scene_sessions = c3d.c3d_sessions(
|
|
92
|
+
session_type="scene",
|
|
93
|
+
scene_id="de704574-b03f-424e-be87-4985f85ed2e8",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Filter to a specific scene version
|
|
97
|
+
scene_sessions = c3d.c3d_sessions(
|
|
98
|
+
session_type="scene",
|
|
99
|
+
scene_version_id=7011,
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Events
|
|
104
|
+
|
|
105
|
+
Retrieve per-event data with session context attached. Events are unnested from sessions — one row per event. Dynamic object IDs are resolved to friendly names, and scene version IDs are resolved to scene names.
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
events = c3d.c3d_events(
|
|
109
|
+
start_date="2025-01-01",
|
|
110
|
+
n=20,
|
|
111
|
+
)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Objective Results
|
|
115
|
+
|
|
116
|
+
Query objective success/failure counts, optionally sliced by version or with step-level detail.
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
# By version (default)
|
|
120
|
+
results = c3d.c3d_objective_results(group_by="version")
|
|
121
|
+
|
|
122
|
+
# Step-level detail
|
|
123
|
+
detailed = c3d.c3d_objective_results(group_by="steps")
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Session Objectives
|
|
127
|
+
|
|
128
|
+
Retrieve per-session objective step results. Returns one row per step per session, with step descriptions and outcomes.
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
session_steps = c3d.c3d_session_objectives(
|
|
132
|
+
start_date="2025-01-01",
|
|
133
|
+
end_date="2025-06-01",
|
|
134
|
+
)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Exit Polls
|
|
138
|
+
|
|
139
|
+
Retrieve exit poll response counts across all hooks and versions. Returns one row per response option per question per version, with human-readable value labels.
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
# All hooks and versions
|
|
143
|
+
polls = c3d.c3d_exitpoll()
|
|
144
|
+
|
|
145
|
+
# Filter to a specific hook and version
|
|
146
|
+
polls = c3d.c3d_exitpoll(hook="end_questions", version=3)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Configuration
|
|
150
|
+
|
|
151
|
+
### Timeout
|
|
152
|
+
|
|
153
|
+
The default request timeout is 30 seconds per API call. To increase it:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
import cognitive3dpy as c3d
|
|
157
|
+
|
|
158
|
+
c3d.c3d_set_timeout(60) # 60 seconds
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Or set the `C3D_TIMEOUT` environment variable before your session starts:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
export C3D_TIMEOUT=60
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`c3d_set_timeout()` takes effect immediately. The environment variable is read once on first import, so it must be set before importing the package.
|
|
168
|
+
|
|
169
|
+
## Key Features
|
|
170
|
+
|
|
171
|
+
- **Compact mode** — `c3d_sessions(compact=True)` (default) returns ~40 curated columns; `compact=False` returns everything
|
|
172
|
+
- **Scene sessions** — `session_type="scene"` queries the latest version of each scene by default, giving the full project picture split by scene
|
|
173
|
+
- **Automatic name resolution** — dynamic object SDK IDs are resolved to friendly names in events and objective steps; scene version IDs are resolved to scene names
|
|
174
|
+
- **Date defaults** — all functions default to the last 30 days when no date range is specified
|
|
175
|
+
- **Column naming** — top-level API fields use `snake_case`; Cognitive3D properties retain their `c3d_` prefix (e.g., `c3d_metrics_fps_score`)
|
|
176
|
+
- **Polars-native** — returns `polars.DataFrame` by default; pass `output="pandas"` for a pandas DataFrame
|
|
177
|
+
- **Session filtering** — `exclude_test`, `exclude_idle`, `min_duration` parameters across functions
|
|
178
|
+
|
|
179
|
+
## Common Options
|
|
180
|
+
|
|
181
|
+
All data-fetching functions support these session filters:
|
|
182
|
+
|
|
183
|
+
- `exclude_test` / `exclude_idle` — filter out test and junk sessions (default `True`)
|
|
184
|
+
- `start_date` / `end_date` — date range as a `date`/`datetime` object, epoch timestamp, or `"YYYY-MM-DD"` string
|
|
185
|
+
- `min_duration` — minimum session duration in seconds
|
|
186
|
+
- `project_id` — override the default set by `c3d_project()`
|
|
187
|
+
- `output` — `"polars"` (default) or `"pandas"`
|
|
188
|
+
- `warn_empty` — emit a `UserWarning` when 0 rows are returned (default `True`); set to `False` to suppress
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "cognitive3dpy"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "Python client for the Cognitive3D analytics API."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Mona", email = "monajhzhu@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"httpx>=0.28.1",
|
|
12
|
+
"polars>=1.38.1",
|
|
13
|
+
"python-dateutil>=2.9.0.post0",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
pandas = [
|
|
18
|
+
"pandas>=3.0",
|
|
19
|
+
"pyarrow>=23.0.1",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[build-system]
|
|
23
|
+
requires = ["uv_build>=0.10.6,<0.11.0"]
|
|
24
|
+
build-backend = "uv_build"
|
|
25
|
+
|
|
26
|
+
[dependency-groups]
|
|
27
|
+
dev = [
|
|
28
|
+
"ipykernel>=7.2.0",
|
|
29
|
+
"mypy>=1.19.1",
|
|
30
|
+
"pytest>=9.0.2",
|
|
31
|
+
"pytest-cov>=7.0.0",
|
|
32
|
+
"python-semantic-release>=9.0.0",
|
|
33
|
+
"respx>=0.22.0",
|
|
34
|
+
"ruff>=0.15.2",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[tool.ruff]
|
|
38
|
+
line-length = 88
|
|
39
|
+
target-version = "py311"
|
|
40
|
+
|
|
41
|
+
[tool.ruff.lint]
|
|
42
|
+
select = ["E", "F", "I", "UP"]
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
testpaths = ["tests"]
|
|
46
|
+
|
|
47
|
+
[tool.semantic_release]
|
|
48
|
+
version_toml = ["pyproject.toml:project.version"]
|
|
49
|
+
branch = "main"
|
|
50
|
+
commit_message = "chore(release): v{version} [skip ci]"
|
|
51
|
+
|
|
52
|
+
[tool.semantic_release.changelog]
|
|
53
|
+
exclude_commit_patterns = ["^chore"]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""cognitive3dpy — Python client for the Cognitive3D Analytics REST API."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import version
|
|
4
|
+
|
|
5
|
+
__version__ = version("cognitive3dpy")
|
|
6
|
+
|
|
7
|
+
from cognitive3dpy._client import (
|
|
8
|
+
C3DAPIError,
|
|
9
|
+
C3DAuthError,
|
|
10
|
+
C3DError,
|
|
11
|
+
C3DNotFoundError,
|
|
12
|
+
c3d_set_timeout,
|
|
13
|
+
)
|
|
14
|
+
from cognitive3dpy.auth import c3d_auth, c3d_project
|
|
15
|
+
from cognitive3dpy.events import c3d_events
|
|
16
|
+
from cognitive3dpy.exitpoll import c3d_exitpoll
|
|
17
|
+
from cognitive3dpy.objectives import c3d_objective_results
|
|
18
|
+
from cognitive3dpy.session_objectives import c3d_session_objectives
|
|
19
|
+
from cognitive3dpy.sessions import c3d_sessions
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
# Auth
|
|
23
|
+
"c3d_auth",
|
|
24
|
+
"c3d_project",
|
|
25
|
+
# Data functions
|
|
26
|
+
"c3d_sessions",
|
|
27
|
+
"c3d_events",
|
|
28
|
+
"c3d_objective_results",
|
|
29
|
+
"c3d_session_objectives",
|
|
30
|
+
"c3d_exitpoll",
|
|
31
|
+
# Config
|
|
32
|
+
"c3d_set_timeout",
|
|
33
|
+
# Exceptions
|
|
34
|
+
"C3DError",
|
|
35
|
+
"C3DAuthError",
|
|
36
|
+
"C3DNotFoundError",
|
|
37
|
+
"C3DAPIError",
|
|
38
|
+
]
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""HTTP client wrapper for the Cognitive3D API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import atexit
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import time
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
from cognitive3dpy.auth import get_api_key
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
_RETRYABLE_STATUS = {429, 500, 502, 503, 504}
|
|
17
|
+
_MAX_RETRIES = 3
|
|
18
|
+
_BACKOFF_BASE = 1.0 # seconds
|
|
19
|
+
|
|
20
|
+
BASE_URL = "https://api.cognitive3d.com"
|
|
21
|
+
_DEFAULT_TIMEOUT = 30.0
|
|
22
|
+
|
|
23
|
+
_client: httpx.Client | None = None
|
|
24
|
+
_timeout: float = float(os.environ.get("C3D_TIMEOUT", _DEFAULT_TIMEOUT))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _close_client() -> None:
|
|
28
|
+
if _client is not None and not _client.is_closed:
|
|
29
|
+
_client.close()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
atexit.register(_close_client)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class C3DError(Exception):
|
|
36
|
+
"""Base exception for Cognitive3D API errors."""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class C3DAuthError(C3DError):
|
|
40
|
+
"""Raised on 401 Unauthorized."""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class C3DNotFoundError(C3DError):
|
|
44
|
+
"""Raised on 404 Not Found."""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class C3DAPIError(C3DError):
|
|
48
|
+
"""Raised on any other non-2xx response."""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _get_client() -> httpx.Client:
|
|
52
|
+
"""Return a reusable httpx client, creating one if needed."""
|
|
53
|
+
global _client
|
|
54
|
+
if _client is None or _client.is_closed:
|
|
55
|
+
_client = httpx.Client(base_url=BASE_URL, timeout=_timeout)
|
|
56
|
+
return _client
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def c3d_set_timeout(seconds: float) -> None:
|
|
60
|
+
"""Set the HTTP request timeout for all API calls.
|
|
61
|
+
|
|
62
|
+
Takes effect immediately by recreating the underlying HTTP client.
|
|
63
|
+
Can also be configured via the ``C3D_TIMEOUT`` environment variable
|
|
64
|
+
before the first API call.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
seconds : float
|
|
69
|
+
Timeout in seconds per request. Default is 30.0.
|
|
70
|
+
"""
|
|
71
|
+
global _timeout, _client
|
|
72
|
+
_timeout = float(seconds)
|
|
73
|
+
if _client is not None and not _client.is_closed:
|
|
74
|
+
_client.close()
|
|
75
|
+
_client = None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _build_headers() -> dict[str, str]:
|
|
79
|
+
"""Build request headers with the stored API key."""
|
|
80
|
+
return {
|
|
81
|
+
"Authorization": get_api_key(),
|
|
82
|
+
"Content-Type": "application/json",
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _raise_for_status(response: httpx.Response) -> None:
|
|
87
|
+
"""Raise a typed exception for non-2xx responses."""
|
|
88
|
+
if response.is_success:
|
|
89
|
+
return
|
|
90
|
+
msg = f"{response.status_code}: {response.text}"
|
|
91
|
+
if response.status_code == 401:
|
|
92
|
+
raise C3DAuthError(msg)
|
|
93
|
+
if response.status_code == 404:
|
|
94
|
+
raise C3DNotFoundError(msg)
|
|
95
|
+
raise C3DAPIError(msg)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _execute_with_retry(fn) -> httpx.Response:
|
|
99
|
+
"""Execute a request callable with exponential backoff on transient errors."""
|
|
100
|
+
last_exc: Exception | None = None
|
|
101
|
+
for attempt in range(_MAX_RETRIES):
|
|
102
|
+
try:
|
|
103
|
+
response = fn()
|
|
104
|
+
if response.status_code not in _RETRYABLE_STATUS:
|
|
105
|
+
return response
|
|
106
|
+
last_exc = C3DAPIError(f"{response.status_code}: {response.text}")
|
|
107
|
+
except httpx.TransportError as exc:
|
|
108
|
+
last_exc = exc
|
|
109
|
+
|
|
110
|
+
wait = _BACKOFF_BASE * (2**attempt)
|
|
111
|
+
logger.warning(
|
|
112
|
+
"Request failed (attempt %d/%d), retrying in %.1fs...",
|
|
113
|
+
attempt + 1,
|
|
114
|
+
_MAX_RETRIES,
|
|
115
|
+
wait,
|
|
116
|
+
)
|
|
117
|
+
time.sleep(wait)
|
|
118
|
+
|
|
119
|
+
raise last_exc
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def c3d_request(endpoint: str, body: dict) -> dict | list:
|
|
123
|
+
"""POST to the API and return the parsed JSON response."""
|
|
124
|
+
client = _get_client()
|
|
125
|
+
response = _execute_with_retry(
|
|
126
|
+
lambda: client.post(endpoint, headers=_build_headers(), json=body)
|
|
127
|
+
)
|
|
128
|
+
_raise_for_status(response)
|
|
129
|
+
return response.json()
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def c3d_get(endpoint: str) -> dict | list:
|
|
133
|
+
"""GET from the API and return the parsed JSON response."""
|
|
134
|
+
client = _get_client()
|
|
135
|
+
response = _execute_with_retry(
|
|
136
|
+
lambda: client.get(endpoint, headers=_build_headers())
|
|
137
|
+
)
|
|
138
|
+
_raise_for_status(response)
|
|
139
|
+
return response.json()
|