hyperstudy 0.2.3__py3-none-any.whl → 0.3.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.
hyperstudy/__init__.py CHANGED
@@ -6,6 +6,17 @@ Usage::
6
6
 
7
7
  hs = hyperstudy.HyperStudy(api_key="hst_live_...")
8
8
  events = hs.get_events("experiment_id")
9
+
10
+ Building experiments programmatically::
11
+
12
+ from hyperstudy import HyperStudy, Experiment, State, show_text
13
+
14
+ exp = Experiment(
15
+ name="Welcome study",
16
+ states=[State(id="s1", focus_component=show_text("Hello"))],
17
+ )
18
+ hs = HyperStudy(api_key="hst_live_...")
19
+ info = hs.create_experiment(experiment=exp)
9
20
  """
10
21
 
11
22
  from ._types import DataType, RatingKind, Scope
@@ -18,8 +29,30 @@ from .exceptions import (
18
29
  ServerError,
19
30
  ValidationError,
20
31
  )
32
+ from .models import (
33
+ ComponentType,
34
+ DisconnectTimeout,
35
+ Experiment,
36
+ FocusComponent,
37
+ GlobalComponentType,
38
+ InstructionsPage,
39
+ PostExperimentQuestionnaire,
40
+ Role,
41
+ State,
42
+ TransitionRules,
43
+ WaitingRoomConfig,
44
+ likert_scale,
45
+ multiple_choice,
46
+ ranking,
47
+ show_image,
48
+ show_text,
49
+ show_video,
50
+ text_input,
51
+ vas_rating,
52
+ waiting,
53
+ )
21
54
 
22
- __version__ = "0.2.3"
55
+ __version__ = "0.3.0"
23
56
 
24
57
  __all__ = [
25
58
  "HyperStudy",
@@ -35,4 +68,26 @@ __all__ = [
35
68
  "Scope",
36
69
  "DataType",
37
70
  "RatingKind",
71
+ "ComponentType",
72
+ "GlobalComponentType",
73
+ # Experiment builders
74
+ "Experiment",
75
+ "State",
76
+ "FocusComponent",
77
+ "Role",
78
+ "TransitionRules",
79
+ "WaitingRoomConfig",
80
+ "DisconnectTimeout",
81
+ "InstructionsPage",
82
+ "PostExperimentQuestionnaire",
83
+ # Component factories
84
+ "show_text",
85
+ "show_image",
86
+ "show_video",
87
+ "vas_rating",
88
+ "text_input",
89
+ "multiple_choice",
90
+ "waiting",
91
+ "likert_scale",
92
+ "ranking",
38
93
  ]
hyperstudy/experiments.py CHANGED
@@ -2,12 +2,50 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import Any
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from pydantic import BaseModel
8
+ from pydantic.alias_generators import to_camel
6
9
 
7
10
  from ._display import ExperimentInfo
8
11
  from ._http import HttpTransport
9
12
  from ._pagination import fetch_all_pages
10
13
 
14
+ if TYPE_CHECKING:
15
+ from .models import Experiment
16
+
17
+
18
+ def _build_experiment_payload(
19
+ experiment: "Experiment | dict[str, Any] | None" = None,
20
+ **overrides: Any,
21
+ ) -> dict[str, Any]:
22
+ """Build a wire-ready dict from an Experiment instance or raw dict.
23
+
24
+ - ``Experiment`` → ``.model_dump(by_alias=True, exclude_none=True)``
25
+ - ``dict`` → shallow copy
26
+ - ``None`` → empty dict
27
+
28
+ ``overrides`` are merged on top (caller wins). Snake_case keys in
29
+ overrides are translated to camelCase so they actually override the
30
+ aliased fields of the dumped ``Experiment`` (otherwise a kwarg like
31
+ ``required_participants=5`` would leave the builder's
32
+ ``requiredParticipants`` in place and ALSO add ``required_participants``
33
+ to the wire payload — both keys would go).
34
+ """
35
+ if experiment is None:
36
+ payload: dict[str, Any] = {}
37
+ elif isinstance(experiment, BaseModel):
38
+ payload = experiment.model_dump(by_alias=True, exclude_none=True)
39
+ elif isinstance(experiment, dict):
40
+ payload = dict(experiment)
41
+ else:
42
+ raise TypeError(
43
+ f"experiment must be an Experiment or dict, got {type(experiment).__name__}"
44
+ )
45
+ for key, value in overrides.items():
46
+ payload[to_camel(key)] = value
47
+ return payload
48
+
11
49
 
12
50
  class ExperimentMixin:
13
51
  """Methods for experiment management. Mixed into the main client."""
@@ -79,33 +117,92 @@ class ExperimentMixin:
79
117
  # Write
80
118
  # ------------------------------------------------------------------
81
119
 
82
- def create_experiment(self, *, name: str, **kwargs) -> ExperimentInfo:
120
+ def create_experiment(
121
+ self,
122
+ *,
123
+ experiment: "Experiment | None" = None,
124
+ name: str | None = None,
125
+ **kwargs,
126
+ ) -> ExperimentInfo:
83
127
  """Create a new experiment.
84
128
 
129
+ Two equivalent ways to call this:
130
+
131
+ - **Typed builder** (recommended): pass an :class:`Experiment` instance
132
+ via the ``experiment`` argument. Snake_case fields on the model are
133
+ converted to the camelCase wire format automatically. Fields added
134
+ via ``extra="allow"`` (unknown to the SDK's typed shape) and entries
135
+ in the per-component ``config`` dict pass through *verbatim* — use
136
+ camelCase keys for those.
137
+ - **Raw kwargs**: pass ``name=`` (required) and any additional fields
138
+ as keyword arguments. Backwards-compatible with hyperstudy <= 0.2.x.
139
+
140
+ When both forms are mixed, keyword arguments override fields from
141
+ ``experiment`` — useful for ad-hoc overrides.
142
+
85
143
  Args:
86
- name: Experiment name (required).
87
- **kwargs: Additional fields (config, states, roles, description, etc.).
144
+ experiment: Typed experiment definition.
145
+ name: Experiment name (required when ``experiment`` is omitted).
146
+ **kwargs: Additional fields, or overrides applied on top of
147
+ ``experiment``.
88
148
 
89
149
  Returns:
90
150
  The created :class:`ExperimentInfo`.
91
151
  """
92
- payload = {"name": name, **kwargs}
152
+ if name is not None:
153
+ kwargs["name"] = name
154
+ payload = _build_experiment_payload(experiment, **kwargs)
155
+ if not payload.get("name"):
156
+ raise TypeError(
157
+ "create_experiment requires a 'name' "
158
+ "(pass name=... or experiment=Experiment(name=...))"
159
+ )
93
160
  body = self._transport.post("experiments", json=payload)
94
161
  data = body.get("data", [])
95
162
  record = data[0] if isinstance(data, list) and data else data
96
163
  return ExperimentInfo(record)
97
164
 
98
- def update_experiment(self, experiment_id: str, **kwargs) -> dict[str, Any]:
165
+ def update_experiment(
166
+ self,
167
+ experiment_id: str,
168
+ *,
169
+ experiment: "Experiment | None" = None,
170
+ **kwargs,
171
+ ) -> dict[str, Any]:
99
172
  """Update an experiment.
100
173
 
101
174
  Args:
102
175
  experiment_id: ID of the experiment to update.
103
- **kwargs: Fields to update (name, config, states, etc.).
176
+ experiment: Optional typed :class:`Experiment` carrying the new
177
+ fields. Snake_case → camelCase conversion is applied.
178
+ **kwargs: Fields to update, or overrides applied on top of
179
+ ``experiment``.
104
180
 
105
181
  Returns:
106
182
  Update confirmation dict.
107
183
  """
108
- body = self._transport.put(f"experiments/{experiment_id}", json=kwargs)
184
+ payload = _build_experiment_payload(experiment, **kwargs)
185
+ body = self._transport.put(f"experiments/{experiment_id}", json=payload)
186
+ data = body.get("data", [])
187
+ return data[0] if isinstance(data, list) and data else data
188
+
189
+ def validate_experiment(
190
+ self,
191
+ experiment: "Experiment | dict[str, Any]",
192
+ ) -> dict[str, Any]:
193
+ """Dry-run validation of an experiment definition against the backend.
194
+
195
+ Returns the ``ValidationResultResponse`` body — typically
196
+ ``{"valid": True}`` on success, or ``{"valid": False, "errors": [...]}``
197
+ with a list of issues.
198
+
199
+ Args:
200
+ experiment: An :class:`Experiment` or a raw config dict.
201
+ """
202
+ if experiment is None:
203
+ raise TypeError("validate_experiment requires an Experiment or dict")
204
+ payload = _build_experiment_payload(experiment)
205
+ body = self._transport.post("experiments/validate", json=payload)
109
206
  data = body.get("data", [])
110
207
  return data[0] if isinstance(data, list) and data else data
111
208
 
hyperstudy/models.py ADDED
@@ -0,0 +1,327 @@
1
+ """Typed builders for HyperStudy experiment definitions.
2
+
3
+ Mirrors the JSON schema at ``shared/schemas/experiment.schema.json`` in the
4
+ main repo. The outer shape (Experiment / State / FocusComponent / Role) is
5
+ typed; per-component ``config`` payloads are validated server-side and remain
6
+ ``dict[str, Any]`` here on purpose. Unknown top-level fields are preserved
7
+ via ``extra="allow"`` so the SDK does not block on backend additions.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import uuid
13
+ from enum import Enum
14
+ from typing import Any, Optional
15
+
16
+ from pydantic import BaseModel, ConfigDict, Field
17
+ from pydantic.alias_generators import to_camel
18
+
19
+
20
+ def _new_id() -> str:
21
+ return uuid.uuid4().hex[:8]
22
+
23
+
24
+ class ComponentType(str, Enum):
25
+ """Focus component types (one per state)."""
26
+
27
+ SHOW_TEXT = "showtext"
28
+ SHOW_IMAGE = "showimage"
29
+ SHOW_VIDEO = "showvideo"
30
+ VAS_RATING = "vasrating"
31
+ TEXT_INPUT = "textinput"
32
+ MULTIPLE_CHOICE = "multiplechoice"
33
+ WAITING = "waiting"
34
+ CODE = "code"
35
+ CONTINUOUS_RATING = "continuousrating"
36
+ VIDEO_CHAT = "videochat"
37
+ TEXT_CHAT = "textchat"
38
+ RAPID_RATE = "rapidrate"
39
+ AUDIO_RECORDING = "audiorecording"
40
+ SPARSE_RATING = "sparserating"
41
+ TRIGGER = "trigger"
42
+ RANKING = "ranking"
43
+ SCANNER_PULSE_RECORDER = "scannerpulserecorder"
44
+ LIKERT_SCALE = "likertscale"
45
+ GAZE_OVERLAY = "gazeoverlay"
46
+
47
+
48
+ class GlobalComponentType(str, Enum):
49
+ """Component types that may run as global (persistent) components."""
50
+
51
+ CONTINUOUS_RATING = "continuousrating"
52
+ VIDEO_CHAT = "videochat"
53
+ TEXT_CHAT = "textchat"
54
+ SPARSE_RATING = "sparserating"
55
+ SCANNER_PULSE_RECORDER = "scannerpulserecorder"
56
+ GAZE_OVERLAY = "gazeoverlay"
57
+
58
+
59
+ _BASE_CONFIG = ConfigDict(
60
+ populate_by_name=True,
61
+ alias_generator=to_camel,
62
+ extra="allow",
63
+ use_enum_values=True,
64
+ )
65
+
66
+
67
+ class _Model(BaseModel):
68
+ model_config = _BASE_CONFIG
69
+
70
+
71
+ class FocusComponent(_Model):
72
+ """Primary component displayed in a single state.
73
+
74
+ ``config`` is component-type-specific and validated by the backend's
75
+ ``componentSchemaValidator``. Use the factory helpers in this module
76
+ (``show_text``, ``vas_rating``, ...) for ergonomic construction.
77
+ """
78
+
79
+ type: ComponentType
80
+ config: dict[str, Any] = Field(default_factory=dict)
81
+ id: Optional[str] = None
82
+
83
+
84
+ class TransitionRules(_Model):
85
+ """Rules governing how the experiment transitions from a state."""
86
+
87
+ type: Optional[str] = None
88
+ duration_ms: Optional[int] = Field(default=None, ge=0)
89
+
90
+
91
+ class State(_Model):
92
+ """An experiment state — a single screen/phase."""
93
+
94
+ id: str
95
+ name: Optional[str] = None
96
+ order: Optional[int] = Field(default=None, ge=0)
97
+ focus_component: Optional[FocusComponent] = None
98
+ transition_rules: Optional[TransitionRules] = None
99
+ global_components_visibility: Optional[dict[str, bool]] = None
100
+
101
+
102
+ class Role(_Model):
103
+ """Participant role definition for multi-participant experiments."""
104
+
105
+ name: Optional[str] = None
106
+ participant_count: Optional[int] = Field(default=None, ge=0)
107
+
108
+
109
+ class WaitingRoomConfig(_Model):
110
+ max_wait_time_ms: Optional[int] = Field(default=None, ge=0)
111
+ countdown_time_ms: Optional[int] = Field(default=None, ge=0)
112
+
113
+
114
+ class DisconnectTimeout(_Model):
115
+ enabled: Optional[bool] = None
116
+ duration_ms: Optional[int] = Field(default=None, ge=0)
117
+ auto_reconnect_delay: Optional[int] = Field(default=None, ge=0)
118
+
119
+
120
+ class InstructionsPage(_Model):
121
+ title: Optional[str] = None
122
+ content: Optional[str] = None
123
+
124
+
125
+ class PostExperimentQuestionnaire(_Model):
126
+ enabled: Optional[bool] = None
127
+ questions: Optional[list[dict[str, Any]]] = None
128
+
129
+
130
+ class Experiment(_Model):
131
+ """Root experiment definition. Only ``name`` is required.
132
+
133
+ Use ``.model_dump(by_alias=True, exclude_none=True)`` to produce a
134
+ wire-ready payload, or pass directly to ``client.create_experiment(
135
+ experiment=exp)``.
136
+ """
137
+
138
+ name: str = Field(min_length=1)
139
+ description: Optional[str] = None
140
+ required_participants: Optional[int] = Field(default=None, ge=1)
141
+ randomize_states: Optional[bool] = None
142
+ states: Optional[list[State]] = None
143
+ roles: Optional[dict[str, Role]] = None
144
+ global_components: Optional[dict[str, dict[str, Any]]] = None
145
+ variables: Optional[dict[str, Any]] = None
146
+ waiting_room_config: Optional[WaitingRoomConfig] = None
147
+ disconnect_timeout: Optional[DisconnectTimeout] = None
148
+ completion_screen_duration_ms: Optional[int] = Field(default=None, ge=0)
149
+ consent_form_enabled: Optional[bool] = None
150
+ consent_form_title: Optional[str] = None
151
+ consent_form_content: Optional[str] = None
152
+ instructions_enabled: Optional[bool] = None
153
+ instructions_title: Optional[str] = None
154
+ instructions_content: Optional[str] = None
155
+ instructions_pages: Optional[list[InstructionsPage]] = None
156
+ post_experiment_questionnaire: Optional[PostExperimentQuestionnaire] = None
157
+
158
+
159
+ # ---------------------------------------------------------------------------
160
+ # Component factory helpers
161
+ # ---------------------------------------------------------------------------
162
+ #
163
+ # Each factory builds a ``FocusComponent`` with the standard config keys for
164
+ # its component type. Extra config keys pass through via ``**extra`` so users
165
+ # can set any field the backend accepts without needing a new factory.
166
+
167
+
168
+ # Across all factories, ``**extra`` is spread FIRST so factory-set config
169
+ # keys win on collision. A user who accidentally passes the camelCase
170
+ # version of a documented snake_case arg (e.g. ``outputVariable=`` instead
171
+ # of ``output_variable=``) gets the factory's value, not their typo.
172
+
173
+
174
+ def show_text(text: str, *, id: Optional[str] = None, **extra: Any) -> FocusComponent:
175
+ """Build a ``showtext`` focus component."""
176
+ return FocusComponent(
177
+ type=ComponentType.SHOW_TEXT,
178
+ config={**extra, "text": text},
179
+ id=id or _new_id(),
180
+ )
181
+
182
+
183
+ def show_image(url: str, *, id: Optional[str] = None, **extra: Any) -> FocusComponent:
184
+ """Build a ``showimage`` focus component."""
185
+ return FocusComponent(
186
+ type=ComponentType.SHOW_IMAGE,
187
+ config={**extra, "url": url},
188
+ id=id or _new_id(),
189
+ )
190
+
191
+
192
+ def show_video(url: str, *, id: Optional[str] = None, **extra: Any) -> FocusComponent:
193
+ """Build a ``showvideo`` focus component."""
194
+ return FocusComponent(
195
+ type=ComponentType.SHOW_VIDEO,
196
+ config={**extra, "url": url},
197
+ id=id or _new_id(),
198
+ )
199
+
200
+
201
+ def vas_rating(
202
+ prompt: str,
203
+ *,
204
+ output_variable: str,
205
+ id: Optional[str] = None,
206
+ **extra: Any,
207
+ ) -> FocusComponent:
208
+ """Build a ``vasrating`` (visual analog scale) focus component."""
209
+ return FocusComponent(
210
+ type=ComponentType.VAS_RATING,
211
+ config={**extra, "prompt": prompt, "outputVariable": output_variable},
212
+ id=id or _new_id(),
213
+ )
214
+
215
+
216
+ def text_input(
217
+ prompt: str,
218
+ *,
219
+ output_variable: str,
220
+ id: Optional[str] = None,
221
+ **extra: Any,
222
+ ) -> FocusComponent:
223
+ """Build a ``textinput`` focus component."""
224
+ return FocusComponent(
225
+ type=ComponentType.TEXT_INPUT,
226
+ config={**extra, "prompt": prompt, "outputVariable": output_variable},
227
+ id=id or _new_id(),
228
+ )
229
+
230
+
231
+ def multiple_choice(
232
+ prompt: str,
233
+ options: list[str],
234
+ *,
235
+ output_variable: str,
236
+ id: Optional[str] = None,
237
+ **extra: Any,
238
+ ) -> FocusComponent:
239
+ """Build a ``multiplechoice`` focus component."""
240
+ return FocusComponent(
241
+ type=ComponentType.MULTIPLE_CHOICE,
242
+ config={
243
+ **extra,
244
+ "prompt": prompt,
245
+ "options": list(options),
246
+ "outputVariable": output_variable,
247
+ },
248
+ id=id or _new_id(),
249
+ )
250
+
251
+
252
+ def waiting(duration_ms: int, *, id: Optional[str] = None, **extra: Any) -> FocusComponent:
253
+ """Build a ``waiting`` focus component."""
254
+ return FocusComponent(
255
+ type=ComponentType.WAITING,
256
+ config={**extra, "durationMs": int(duration_ms)},
257
+ id=id or _new_id(),
258
+ )
259
+
260
+
261
+ def likert_scale(
262
+ prompt: str,
263
+ *,
264
+ output_variable: str,
265
+ scale_points: int = 7,
266
+ id: Optional[str] = None,
267
+ **extra: Any,
268
+ ) -> FocusComponent:
269
+ """Build a ``likertscale`` focus component."""
270
+ return FocusComponent(
271
+ type=ComponentType.LIKERT_SCALE,
272
+ config={
273
+ **extra,
274
+ "prompt": prompt,
275
+ "outputVariable": output_variable,
276
+ "scalePoints": int(scale_points),
277
+ },
278
+ id=id or _new_id(),
279
+ )
280
+
281
+
282
+ def ranking(
283
+ prompt: str,
284
+ options: list[str],
285
+ *,
286
+ output_variable: str,
287
+ id: Optional[str] = None,
288
+ **extra: Any,
289
+ ) -> FocusComponent:
290
+ """Build a ``ranking`` focus component."""
291
+ return FocusComponent(
292
+ type=ComponentType.RANKING,
293
+ config={
294
+ **extra,
295
+ "prompt": prompt,
296
+ "options": list(options),
297
+ "outputVariable": output_variable,
298
+ },
299
+ id=id or _new_id(),
300
+ )
301
+
302
+
303
+ __all__ = [
304
+ # Enums
305
+ "ComponentType",
306
+ "GlobalComponentType",
307
+ # Models
308
+ "Experiment",
309
+ "State",
310
+ "FocusComponent",
311
+ "TransitionRules",
312
+ "Role",
313
+ "WaitingRoomConfig",
314
+ "DisconnectTimeout",
315
+ "InstructionsPage",
316
+ "PostExperimentQuestionnaire",
317
+ # Factories
318
+ "show_text",
319
+ "show_image",
320
+ "show_video",
321
+ "vas_rating",
322
+ "text_input",
323
+ "multiple_choice",
324
+ "waiting",
325
+ "likert_scale",
326
+ "ranking",
327
+ ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperstudy
3
- Version: 0.2.3
3
+ Version: 0.3.0
4
4
  Summary: Python SDK for the HyperStudy experiment platform API
5
5
  Project-URL: Homepage, https://hyperstudy.io
6
6
  Project-URL: Documentation, https://docs.hyperstudy.io/developers/python-sdk
@@ -21,6 +21,7 @@ Classifier: Programming Language :: Python :: 3.13
21
21
  Classifier: Topic :: Scientific/Engineering
22
22
  Requires-Python: >=3.9
23
23
  Requires-Dist: pandas>=1.5.0
24
+ Requires-Dist: pydantic>=2.5
24
25
  Requires-Dist: requests>=2.28.0
25
26
  Requires-Dist: tqdm>=4.60.0
26
27
  Provides-Extra: dev
@@ -125,12 +126,41 @@ experiments = hs.list_experiments()
125
126
  # Get details (with rich display in notebooks)
126
127
  exp = hs.get_experiment("exp_id")
127
128
 
128
- # Create / update / delete
129
+ # Quick create / update / delete (kwargs form, backwards-compatible)
129
130
  new_exp = hs.create_experiment(name="My Study", description="...")
130
131
  hs.update_experiment("exp_id", name="Updated Name")
131
132
  hs.delete_experiment("exp_id")
132
133
  ```
133
134
 
135
+ ### Building experiments programmatically (0.3.0+)
136
+
137
+ For full experiment definitions — states, components, roles — use the typed `Experiment` builder. Snake_case Python fields convert to the camelCase wire format automatically.
138
+
139
+ ```python
140
+ from hyperstudy import Experiment, State, Role, show_text, vas_rating
141
+
142
+ exp = Experiment(
143
+ name="Two-person study",
144
+ required_participants=2,
145
+ states=[
146
+ State(id="intro", focus_component=show_text("Welcome")),
147
+ State(
148
+ id="rate",
149
+ focus_component=vas_rating("Rate the clip", output_variable="rating"),
150
+ ),
151
+ ],
152
+ roles={"speaker": Role(name="Speaker", participant_count=1)},
153
+ )
154
+
155
+ # Dry-run validation against the backend.
156
+ print(hs.validate_experiment(exp)) # → {"valid": True, ...}
157
+
158
+ # Create.
159
+ info = hs.create_experiment(experiment=exp)
160
+ ```
161
+
162
+ Component factories cover `show_text`, `show_image`, `show_video`, `vas_rating`, `text_input`, `multiple_choice`, `waiting`, `likert_scale`, `ranking`. For other component types, construct directly: `FocusComponent(type=ComponentType.X, config={...})`. See the [Experiment Authoring guide](https://docs.hyperstudy.io/experimenters/api-access/experiment-authoring) for the full reference.
163
+
134
164
  ## Deployments
135
165
 
136
166
  ```python
@@ -1,4 +1,4 @@
1
- hyperstudy/__init__.py,sha256=-FMsxfDASUokTGR0-iES4eb-SUEjTuThGMqPIg-Imyw,733
1
+ hyperstudy/__init__.py,sha256=DROd0vzdbUR2kzYSH5d8QAWVdPp6fCn1vdRGyNGNQFE,1913
2
2
  hyperstudy/_dataframe.py,sha256=8pjuMG8phMv8vwdY7sgZ-aF_6_o0UMgP2XXNp5muZ0s,3620
3
3
  hyperstudy/_display.py,sha256=5svr4NFOUJkVif57-xJzrXNOSdA5O9tKesu8UhE_jYo,1896
4
4
  hyperstudy/_downloads.py,sha256=MKUjzz9UDfo0V-n22UCdLEH2-tQxBeJMN_JoJ2spIT8,2459
@@ -7,8 +7,9 @@ hyperstudy/_pagination.py,sha256=2NbvNGW_jFQkYquSTi3SNkAmGgsbKop7J9LkGaZWwWA,181
7
7
  hyperstudy/_types.py,sha256=22JTOlM68eGJ4hg4OH-oT-hmARG2CgbhnERJ5hGxHk8,614
8
8
  hyperstudy/client.py,sha256=raAEBNxRsFPr260JWOFlOZNwmFk2SB80L2bsMoojUt0,24376
9
9
  hyperstudy/exceptions.py,sha256=z_1Ngiq-RU-1yyDI_JqvQ5CvYm2jQ_lyJ9TUbqUKKv8,1529
10
- hyperstudy/experiments.py,sha256=BHNf-ctKUA9uzy8Kya11nIyHPrsmOslqC_IJ5t4LxKA,4372
11
- hyperstudy-0.2.3.dist-info/METADATA,sha256=l1DluLkNI41NjQxrCEUAMeWMfi8jZFhXOUdC9RPb_f0,4833
12
- hyperstudy-0.2.3.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
13
- hyperstudy-0.2.3.dist-info/licenses/LICENSE,sha256=WJdwRhBIOiiQ6lyK4l95u5IJYuuT-XxzwYRraS31NME,1067
14
- hyperstudy-0.2.3.dist-info/RECORD,,
10
+ hyperstudy/experiments.py,sha256=jp9kxXwumwuXz2Nq26f62BC_HnNu1lvzpFBpBFHiCeo,8180
11
+ hyperstudy/models.py,sha256=eCXD8-5jEWVhf-eJjZ_C8-D55Uuic3awx04dYn0Q5L8,9411
12
+ hyperstudy-0.3.0.dist-info/METADATA,sha256=P-AVQaBJ2ARQLs4zlErtNdvHCFg760BEz4gE03eye5g,6133
13
+ hyperstudy-0.3.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
14
+ hyperstudy-0.3.0.dist-info/licenses/LICENSE,sha256=WJdwRhBIOiiQ6lyK4l95u5IJYuuT-XxzwYRraS31NME,1067
15
+ hyperstudy-0.3.0.dist-info/RECORD,,