revisit 0.0.26__py2.py3-none-any.whl → 0.0.28__py2.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.
revisit/revisit.py CHANGED
@@ -45,7 +45,6 @@ class _WrappedResponse(_JSONableBaseModel):
45
45
  self.root = self.root.root
46
46
 
47
47
  def set(self, overwrite=True, **kwargs) -> _WrappedResponse:
48
- print('but I got here')
49
48
  for key, value in kwargs.items():
50
49
  # Disallow changing type
51
50
  if key == 'type':
@@ -54,7 +53,6 @@ class _WrappedResponse(_JSONableBaseModel):
54
53
  elif key != 'base':
55
54
  if overwrite is True or (overwrite is False and getattr(self.root, key) is None):
56
55
  setattr(self.root, key, value)
57
- print('do I get here???')
58
56
  # Re-validates the model. Returns the new model.
59
57
  self.root = _validate_response(self.root.__dict__)
60
58
  return self
@@ -169,7 +167,7 @@ class _WrappedComponentBlock(_JSONableBaseModel):
169
167
  order: rvt_models.Order,
170
168
  numSamples: Optional[int] = None,
171
169
  component_function=None
172
- ) -> None:
170
+ ) -> _WrappedComponentBlock:
173
171
 
174
172
  # Initialize components list with blank component if empty
175
173
  make_comp_block = True
@@ -454,9 +452,39 @@ def data(file_path: str) -> List[Any]:
454
452
  return data_rows
455
453
 
456
454
 
457
- def widget(study: _WrappedStudyConfig, revisitPath: str):
458
- if not os.path.isdir(revisitPath):
459
- raise RevisitError(message=f'"{revisitPath}" does not exist.')
455
+ def widget(study: _WrappedStudyConfig, revisitPath: str = '', server=False, pathToLib=''):
456
+
457
+ # If server is set to true, needs to use the correct package path.
458
+ # If server is not true, needs to have a valid revisitPath. I think we already handle that below
459
+
460
+ if server is False and not os.path.isdir(revisitPath):
461
+ raise RevisitError(message=f'"{revisitPath}" does not exist. Specify a correct revisitPath or use the revisti_server module and set "server" to "True".')
462
+
463
+ # Set defaults for when not using server.
464
+ dest_loc = f"{revisitPath}/public/__revisit-widget/assets/"
465
+ dest_loc_react = f"{revisitPath}/src/public/__revisit-widget/assets/"
466
+
467
+ # If using server,
468
+ if server is True:
469
+ # If not path specified, search in current environment.
470
+ if pathToLib == '':
471
+ # Get working directory
472
+ current_dir = os.getcwd()
473
+ # Get parent directory (list of packages installed in .venv)
474
+ parent_dir = os.path.dirname(current_dir)
475
+
476
+ # Construct the path to the revisit_server package
477
+ revisit_server_dir = os.path.join(parent_dir, 'revisit_server')
478
+ if not os.path.isdir(revisit_server_dir):
479
+ raise RevisitError(message='Cannot locate "revisit_server" package in current environment. Specify directory using "pathToLib" or install "revisit_server" in same environment as "revisit" package.')
480
+
481
+ dest_loc = f"{revisit_server_dir}/static/__revisit-widget/assets/"
482
+ else:
483
+ if not os.path.isdir(pathToLib):
484
+ raise RevisitError(message=f'"{pathToLib}" is not a directory.')
485
+ dest_loc = f"{pathToLib}/static/__revisit-widget/assets/"
486
+ if not os.path.isdir(dest_loc):
487
+ raise RevisitError(message=f'"{dest_loc}" is not a directory.')
460
488
 
461
489
  extracted_paths = []
462
490
 
@@ -469,11 +497,12 @@ def widget(study: _WrappedStudyConfig, revisitPath: str):
469
497
  fileName = actual_component.path.split('/')[-1]
470
498
 
471
499
  if actual_component.type == 'react-component':
472
- dest = f"{revisitPath}/src/public/__revisit-widget/assets/{fileName}"
500
+ dest = f"{dest_loc_react}{fileName}"
473
501
  else:
474
- dest = f"{revisitPath}/public/__revisit-widget/assets/{fileName}"
502
+ dest = f"{dest_loc}{fileName}"
475
503
 
476
504
  extracted_paths.append({
505
+ "type": actual_component.type,
477
506
  "src": actual_component.path,
478
507
  "dest": dest
479
508
  })
@@ -485,9 +514,10 @@ def widget(study: _WrappedStudyConfig, revisitPath: str):
485
514
  if uiConfig.helpTextPath is not None:
486
515
 
487
516
  fileName = uiConfig.helpTextPath.split('/')[-1]
488
- dest = f"{revisitPath}/public/__revisit-widget/assets/{fileName}"
517
+ dest = f"{dest_loc}{fileName}"
489
518
 
490
519
  extracted_paths.append({
520
+ "type": 'helpTextPath',
491
521
  "src": uiConfig.helpTextPath,
492
522
  "dest": dest
493
523
  })
@@ -499,9 +529,10 @@ def widget(study: _WrappedStudyConfig, revisitPath: str):
499
529
 
500
530
  fileName = uiConfig.logoPath.split('/')[-1]
501
531
 
502
- dest = f"{revisitPath}/public/__revisit-widget/assets/{fileName}"
532
+ dest = f"{dest_loc}{fileName}"
503
533
 
504
534
  extracted_paths.append({
535
+ "type": 'logoPath',
505
536
  "src": uiConfig.logoPath,
506
537
  "dest": dest
507
538
  })
@@ -511,7 +542,10 @@ def widget(study: _WrappedStudyConfig, revisitPath: str):
511
542
 
512
543
  # Copy all files
513
544
  for item in extracted_paths:
514
- _copy_file(item['src'], item['dest'])
545
+ if item['type'] == 'react-component' and server is True:
546
+ print('Skipping react component when "server" is set to "True".')
547
+ else:
548
+ _copy_file(item['src'], item['dest'])
515
549
 
516
550
  w = _widget.Widget()
517
551
  w.config = json.loads(study.__str__())
@@ -732,7 +766,7 @@ def _recursive_json_permutation(
732
766
  if curr_comp.metadata__ is not None:
733
767
  metadata = {**curr_comp.metadata__, **entry}
734
768
  # Create new component
735
- comp_name = ":".join(f"{key}:{value}" for key, value in entry.items())
769
+ comp_name = "_".join(f"{key}:{value}" for key, value in entry.items())
736
770
  if component_function:
737
771
  new_comp = component_function(**metadata)
738
772
  else:
@@ -1,9 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: revisit
3
- Version: 0.0.26
3
+ Version: 0.0.28
4
4
  Requires-Dist: anywidget
5
5
  Requires-Dist: pandas
6
6
  Requires-Dist: pydantic>=2.10.5
7
+ Requires-Dist: revisit-server>=0.0.1
7
8
  Provides-Extra: dev
8
9
  Requires-Dist: jupyterlab; extra == 'dev'
9
10
  Requires-Dist: watchfiles; extra == 'dev'
@@ -11,7 +12,7 @@ Description-Content-Type: text/markdown
11
12
 
12
13
  # revisit
13
14
 
14
- ## Installation
15
+ # Installation
15
16
 
16
17
  ```sh
17
18
  pip install revisit
@@ -23,34 +24,34 @@ or with [uv](https://github.com/astral-sh/uv):
23
24
  uv add revisit
24
25
  ```
25
26
 
26
- ## Usage
27
+ # Usage
27
28
 
28
29
  The reVISit python package wraps the standard items of the reVISit configuration file with readable, easy-to-use functions. We expose a factory function for each top-level item in the reVISit configuration: `studyMetadata`, `uiConfig`, `components`, `sequence`, and `studyMetadata`. Currently, we do not expose a `baseComponents` function. Instead, base components are still well-defined components and can be passed during the creation of another component. The final configuration will not include base components but will have the expected inherited output.
29
30
 
30
31
  Each factory function takes in the same parameters as the reVISit configuration file. For example, the `studyMetadata` function requires the author, organizations, title, version, and description parameters. Robust error output will help you, the user, understand what is required in each function. For the sake of brevity, we do not list every possible parameter since these are already defined in the current study configuration. Instead, we will show additional required/optional parameters as well as additional methods and other exposed functions.
31
32
 
32
- ### Functions
33
+ The individual classes (`Component`, `Response`, `Sequence`, `StudyMetadata`, `UIConfig`, and `StudyConfig`) should not be created directly. Instead, you should use the corresponding factory functions to insantiate them (`component()`, `response()`, `sequence()`, `studyMetadata()`, `uiConfig()`, and `studyConfig`).
33
34
 
34
- #### `component(component_name__: str, base__: Optional[component], **kwargs: dict) -> Component`
35
+ # Functions
35
36
 
36
- **Description**:
37
+ ## `component(component_name__: str, base__: Optional[component], **kwargs: dict) -> Component`
37
38
 
38
39
  Instantiates a Component class with the given input parameters.
39
40
 
40
- #### **Parameters**:
41
+ ### **Parameters**:
41
42
  | Parameter | Type | Description | Default Value |
42
43
  |-----------|--------|---------------------------------|---------------|
43
44
  | `component_name__` | `str` | Names the component for use in the final configuration file. | _None_ |
44
45
  | `base__` | `Optional[component]` | When a base component is passed, all properties of the base are inherited by the component. Any other specified property during input will override base properties. | _None_ |
45
46
  | `**kwargs` | `dict` | The component function requires any property that the component already requires, such as "type". Refer to the configuration documentation for required properties. | _None_ |
46
47
 
47
- #### **Returns**:
48
+ ### **Returns**:
48
49
  - `Component`: Returns an instantiation of the Component class.
49
50
 
50
- #### **Raises**:
51
+ ### **Raises**:
51
52
  - `RevisitError`: If the required properties are not specified, and exception will be raised.
52
53
 
53
- #### **Example**:
54
+ ### **Example**:
54
55
  ```python
55
56
  import revisit as rvt
56
57
 
@@ -71,24 +72,23 @@ my_other_component = rvt.component(
71
72
  ```
72
73
 
73
74
 
74
- #### `response(**kwargs: dict) -> Response`
75
+ ## `response(**kwargs: dict) -> Response`
75
76
 
76
- **Description**:
77
77
 
78
78
  Instantiates a Response class with the given input parameters.
79
79
 
80
- #### **Parameters**:
80
+ ### **Parameters**:
81
81
  | Parameter | Type | Description | Default Value |
82
82
  |-----------|--------|---------------------------------|---------------|
83
83
  | `**kwargs` | `dict` | The component function requires any property that the component already requires, such as "type". Refer to the configuration documentation for required properties. | _None_ |
84
84
 
85
- #### **Returns**:
85
+ ### **Returns**:
86
86
  - `Response`: Returns an instantiation of the Response class.
87
87
 
88
- #### **Raises**:
88
+ ### **Raises**:
89
89
  - `RevisitError`: If the required properties are not specified, and exception will be raised.
90
90
 
91
- #### **Example**:
91
+ ### **Example**:
92
92
  ```python
93
93
  import revisit as rvt
94
94
 
@@ -102,22 +102,51 @@ my_response = rvt.response(
102
102
  )
103
103
  ```
104
104
 
105
- ### Classes
105
+ ## `studyMetadata(**kwargs: dict) -> StudyMetadata`
106
106
 
107
- #### `Component`
107
+ ## `uiConfig(**kwargs: dict) -> UIConfig`
108
108
 
109
- **Description**:
110
- A brief summary of the class's purpose and functionality.
109
+ ### `studyConfig(studyMetadata: StudyMetadata, uiConfig: UIConfig, sequence: ComponentBlock, schema: str, components: Optional[List[Component]]) -> StudyConfig`
110
+
111
+ Instantiates a the final `StudyConfig` based on the `UIConfig`, `StudyMetadata`, `Sequence`, and `Components` input. Note that the components list is completely optional: using the `studyConfig` factory function automatically populates all components based on their presence in the sequence.
112
+
113
+ ### **Parameters**:
114
+ | Parameter | Type | Description | Default Value |
115
+ |-----------|--------|---------------------------------|---------------|
116
+ | `studyMetadata` | `StudyMetadata` | An instance of the `StudyMetadata` class | _None_ |
117
+ | `uiConfig` | `UIConfig` | An instance of the `UIConfig` class | _None_ |
118
+ | `sequence` | `ComponentBlock` | The top level member of your sequence. | _None_ |
119
+ | `components` | `Optional[List[Component]]` | The list of `Component`s to be added to the config. This is automatically populated based on the inputted sequence | `[]` |
120
+ | `schema` | `str` |The valid `$schema` value for the config | _None_ |
121
+
122
+ ### **Returns**:
123
+ - `StudyConfig`: Returns an instantiation of the StudyConfig class.
124
+
125
+ ### **Raises**:
126
+ - `RevisitError`: If the required properties are not specified, and exception will be raised.
111
127
 
112
- #### **Attributes**:
128
+ ### **Example**:
129
+
130
+ ```python
131
+
132
+ ```
133
+
134
+ # Classes
135
+
136
+ ## `Component`
137
+
138
+ The class that is instantiated when calling the `component` factory function. Used to define the components in the study configuration file.
139
+
140
+ ### **Attributes**:
113
141
  | Attribute | Type | Description | Default Value |
114
142
  |-------------|----------|-------------------------------------|---------------|
115
- | `component_name__` | `type` | Description of attribute 1. | `default` |
116
- | `base__` | `type` | Description of attribute 2. | _None_ |
143
+ | `component_name__` | `str` | Name of the component to be used as the key in the study config. | _None_ |
144
+ | `base__` | `Optional[Component]` | The base component which is inherited by this component. | _None_ |
145
+ | `metadata__` | `Optional[dict]` | A dictionary specifying metadata of the object. This is purely used for additional properties which are not written to the config and may be used to attach arbitrary data to the component without affecting the final output. | _None_ |
117
146
 
147
+ ### **Methods**:
118
148
 
119
- #### **Methods**:
120
- ##### `responses(responses: List[Response]) -> self`
149
+ #### `responses(responses: List[Response]) -> self`
121
150
 
122
151
  **Description**:
123
152
  Sets responses for the component
@@ -133,7 +162,7 @@ Sets responses for the component
133
162
  **Raises**:
134
163
  - `RevisitError`: If the list is not a valid list of responses, raises and exception.
135
164
 
136
- #### **Example**:
165
+ **Example**:
137
166
  ```python
138
167
  my_response=rvt.response(
139
168
  id='my_response',
@@ -163,7 +192,7 @@ Returns the response of the component with the given ID. If the Response does no
163
192
  **Returns**:
164
193
  - `Response`: The response with the given ID.
165
194
 
166
- #### **Examples**:
195
+ **Example**:
167
196
  ```python
168
197
  the_response = my_component.get_response(id='the_response')
169
198
 
@@ -184,7 +213,7 @@ Edits the Response in the Component with the given ID. This is done by creating
184
213
  **Returns**:
185
214
  - `self`: Returns self for method chaining.
186
215
 
187
- #### **Examples**:
216
+ **Example**:
188
217
  ```python
189
218
  test_response = rvt.response(
190
219
  id='test_response',
@@ -246,10 +275,11 @@ Clones the component with the given new component name.
246
275
  |-------------|----------|-------------------------------------|---------------|
247
276
  | `component_name__` | `str` | New component name to assign to cloned component. | _None_ |
248
277
 
278
+
249
279
  **Returns**:
250
280
  - `self`: Returns self for method chaining.
251
281
 
252
- #### **Examples**:
282
+ **Example**:
253
283
  ```python
254
284
  test_response = rvt.response(
255
285
  id='test_response',
@@ -297,19 +327,15 @@ print(component_two)
297
327
  '''
298
328
  ```
299
329
 
300
- #### `Response`
330
+ ## `Response`
301
331
 
302
- **Description**:
303
- A brief summary of the class's purpose and functionality.
332
+ This is the `Responsse` class. When calling the `response` factory function, an instantiation of this class is returned.
304
333
 
305
- #### **Attributes**:
306
- | Attribute | Type | Description | Default Value |
307
- |-------------|----------|-------------------------------------|---------------|
308
- | `component_name__` | `type` | Description of attribute 1. | `default` |
309
- | `base__` | `type` | Description of attribute 2. | _None_ |
334
+ ### **Attributes**:
335
+ _No attributes_
310
336
 
311
337
 
312
- #### **Methods**:
338
+ ### **Methods**:
313
339
 
314
340
  #### `set(**kwargs: dict) -> self`
315
341
 
@@ -325,10 +351,10 @@ Sets the values of the response to the input dictionary. The `type` cannot be ch
325
351
  **Returns**:
326
352
  - `self`: Returns self for method chaining.
327
353
 
328
- #### **Raises**:
354
+ **Raises**:
329
355
  - `RevisitError`: If the user attempts to change the `type` attribute of the response, an exception will be raised. Any invalid inputs for the instantiated response type will also raise an exception.
330
356
 
331
- #### **Examples**:
357
+ **Examples**:
332
358
  ```python
333
359
  response_one = rvt.response(
334
360
  id='r-1',
@@ -369,7 +395,7 @@ _No parameters_
369
395
  **Returns**:
370
396
  - `self`: Returns self for method chaining.
371
397
 
372
- #### **Examples**:
398
+ **Examples**:
373
399
  ```python
374
400
  import random
375
401
  question_1 = rvt.response(
@@ -432,6 +458,279 @@ Expected Output:
432
458
  '''
433
459
  ```
434
460
 
461
+
462
+ ## `ComponentBlock`
463
+
464
+ **Description**:
465
+ The `ComponentBlock` class (also referred to as a "Sequence"). A well-defined sequence simply contains an order and a set of components, with other optional properties. Just as in the nested structure of component blocks in the reVISit study configuration, `ComponentBlock` classes can be added together.
466
+
467
+ A `ComponentBlock` automatically tracks all of its existing `Component` classes. When the `ComponentBlock` is added to the study configuration, all components will automatically be added to the high-level components element of the study config.
468
+
469
+ ### **Attributes**:
470
+ _No attributes_
471
+
472
+
473
+ ### **Methods**:
474
+
475
+ #### `__add__(other: Union[ComponentBlock, Component]) -> self:`
476
+
477
+ **Description**:
478
+
479
+ Adds two `ComponentBlock` or `Component` to the input sequence. When adding two sequences together, the right sequence gets added as a `ComponentBlock` to the list of components of the left sequence. When the right object is an instance of the `Component` class, the component is added to the `ComponentBlock`'s list of components.
480
+
481
+
482
+ **Parameters**:
483
+ | Parameter | Type | Description | Default Value |
484
+ |-------------|----------|-------------------------------------|---------------|
485
+ | `other` | `Union[ComponentBlock, Component]` | Other item adding to left sequence. | _None_ |
486
+
487
+ **Returns**:
488
+ - `self`: Returns self for method chaining.
489
+
490
+ **Raises**:
491
+ - `NotImplemented`: If the right item is not a `Component` or `ComponentBlock`, raises a `NotImplemented` exception.
492
+
493
+ **Examples**:
494
+ ```python
495
+ first_sequence = rvt.sequence(
496
+ order='fixed',
497
+ components=[introduction]
498
+ )
499
+ second_sequence = rvt.sequence(
500
+ order='random',
501
+ components=[comp_one, comp_two]
502
+ )
503
+
504
+ first_sequence = first_sequence + second_sequence
505
+
506
+ print(first_sequence)
507
+ '''
508
+ Expected Output:
509
+ {
510
+ "order": "fixed",
511
+ "components" : [
512
+ "introduction",
513
+ {
514
+ "order": "random"
515
+ "components" : [
516
+ "comp_one",
517
+ "comp_two"
518
+ ]
519
+ }
520
+ ]
521
+ }
522
+ '''
523
+ post_study = rvt.component(
524
+ component_name__='post-study',
525
+ type='markdown',
526
+ path='./post-study.md'
527
+ )
528
+
529
+ first_sequence = first_sequence + post_study
530
+ print(first_sequence)
531
+ '''
532
+ Expected Output:
533
+ {
534
+ "order": "fixed",
535
+ "components" : [
536
+ "introduction",
537
+ {
538
+ "order": "random"
539
+ "components" : [
540
+ "comp_one",
541
+ "comp_two"
542
+ ]
543
+ },
544
+ "post-study"
545
+ ]
546
+ }
547
+ '''
548
+ ```
549
+
550
+ #### `get_component(name: str) -> Component:`
551
+
552
+ **Description**:
553
+
554
+ Fetches the `Component` with the given component name from the sequence.
555
+
556
+
557
+ **Parameters**:
558
+ | Parameter | Type | Description | Default Value |
559
+ |-------------|----------|-------------------------------------|---------------|
560
+ | `name` | `str` | string matching the `component_name__` attribute of the desired `Component`. | _None_ |
561
+
562
+ **Returns**:
563
+ - `Component`: Returns desired `Component`. If no component with specified name is found, returns `None`.
564
+
565
+
566
+ **Examples**:
567
+ ```python
568
+
569
+ sequence = rvt.sequence(
570
+ order='random',
571
+ components=[comp_one, comp_two]
572
+ )
573
+
574
+ print(sequence.get_component(name='comp_two'))
575
+ '''
576
+ {
577
+ "type": "markdown",
578
+ "path": "my_markdown_file.md",
579
+ "response": []
580
+ }
581
+ '''
582
+ ```
583
+
584
+ #### `permute(factors: List[dict], order: 'fixed' | 'latinSquare' | 'random', numSamples: Optional[int], component_function: Optional[Callable]) -> self`
585
+
586
+ **Description**:
587
+ Permutes the the existing components of the sequence over the given `factors`. The permute method can be chained to complex study sequences. By default, the factors are attached as `metadata__` attributes to each component created. If a `component_function` is passed in as an argument, all current factors and `metadata__` of the component will be passed into the component function. The component function must return a valid `Component` instance.
588
+
589
+ **Parameters**:
590
+ | Parameter | Type | Description | Default Value |
591
+ |-------------|----------|-------------------------------------|---------------|
592
+ | `factors` | `List[dict]` | A list of single-key dictionaries to permute over. | _None_ |
593
+ | `order` | `'fixed' \| 'latinSquare' \| 'random' ` |The order to assign to the current permuted component block. | _None_ |
594
+ | `numSamples` | `Optional[int]` | The `numSamples` value to assign to the current permuted block. | _None_ |
595
+ | `component_function` | `Optional[Callable]` | A function defining what component should be generated during permutation. If not provided, the permutation function inherits the existing components in the sequence as new components and applies the factors as metadata. If given, the metadata is passed into the component function as arguments. | _None_ |
596
+
597
+ **Returns**:
598
+ - `self`: Returns self for method chaining.
599
+
600
+ **Raises**:
601
+ - `TypeError`: The `component_function` must be able to take in all metadata of the sequence's existing components as well as the current factors. For example, if you start with a component with no metadata and permute over the factors `[{'key_1':'value_1'}, {'key_1': 'value_2'}]`, the component function should be defined to take in the parameter `key_1`. Otherwise, this will most likely result in a `TypeError` being raised. If you are chaining multiple permute methods after one another, please note that the the set of factors that are passed into the permute function are applied as metadata to the resulting components. Thus, your function must be able to take in all factors as arguments. To avoid this, you can simply use `**kwargs` as your parameter input of your component function.
602
+
603
+ #### **Examples**:
604
+
605
+ **Simple Permutation**
606
+ ```python
607
+
608
+ comp_one = rvt.component(component_name__='my-base', type='markdown', path='./my-markdown.md')
609
+
610
+ sequence = rvt.component(order='fixed',components=[comp_one])
611
+
612
+ sequence.permute(
613
+ factors=[{'condition':'A'}, {'condition':'B'}],
614
+ order='random'
615
+ )
616
+
617
+ print(sequence)
618
+ '''
619
+ Expected Output:
620
+ {
621
+ "order": "random", <--- Since there was only one component in the original sequence, order gets overwritten.
622
+ "components": [
623
+ "my-base_condition:A",
624
+ "my-base_condition:B" <--- Note that the default behavior appends the factors to the name
625
+ ]
626
+ }
627
+
628
+ The two components generated are inherently identical, except with different metadata__ attributes.
629
+ These metadata__ attributes are not outputed into the final JSON study config or seen when printing out
630
+ the individual components.
631
+ '''
632
+
633
+ sequence.permute(
634
+ factors=[{'type':'1'}, {'type': '2'}]
635
+ order='fixed',
636
+ numSamples=1
637
+ )
638
+
639
+ print(sequence)
640
+ '''
641
+ Expected Output:
642
+ {
643
+ "order": "random",
644
+ "components": [
645
+ {
646
+ "order": "fixed", <--- New order gets added to inner most component blocks.
647
+ "components": [
648
+ "my-base_condition:A_type:1",
649
+ "my-base_condition:A_type:2",
650
+ ],
651
+ "numSamples": 1
652
+ },
653
+ {
654
+ "order": "fixed",
655
+ "components": [
656
+ "my-base_condition:B_type:1",
657
+ "my-base_condition:B_type:2",
658
+ ],
659
+ "numSamples": 1
660
+ },
661
+ ]
662
+
663
+ }
664
+ '''
665
+ ```
666
+
667
+ **Using the `component_function`**
668
+
669
+ ```python
670
+ # Defining component function.
671
+ # Takes in kwargs to prevent conflicts with any existing metadata.
672
+ def my_comp_function(**kwargs):
673
+ condition = kwargs.get('condition')
674
+ type_ = kwargs.get('type')
675
+ # If condition and type_ are both defined, return new component.
676
+ if condition is not None and type_ is not None:
677
+ rvt.component(
678
+ type='website'
679
+ component_name__=f"{condition}__{type_}"
680
+ parameters={
681
+ 'condition': condition,
682
+ 'type': type_
683
+ },
684
+ response=[
685
+ rvt.response(
686
+ id=f"response_{condition}_{type_}",
687
+ type="longText",
688
+ prompt=f"How do you feel about condition {condition} and type {type_}?",
689
+ required=True
690
+ )
691
+ ]
692
+ )
693
+
694
+ # If not both defined, return a blank component with "BAD-COMPONENT" name.
695
+ # Useful for debugging
696
+ return rvt.component(type='questionnaire',component_name__="BAD-COMPONENT")
697
+
698
+ sequence = rvt.sequence(order='fixed').permute(
699
+ factors=[{'condition':'A'}, {'condition':'B'}],
700
+ order='random'
701
+ ).permute(
702
+ factors=[{'type':'1'}, {'type': '2'}]
703
+ order='fixed',
704
+ numSamples=1,
705
+ component_function=my_comp_function
706
+ )
707
+ print(sequence)
708
+ '''
709
+ Expected Output:
710
+ {
711
+ "order": "random",
712
+ "components": [
713
+ {
714
+ "order": "fixed", <--- New order gets added to inner most component blocks.
715
+ "components": [
716
+ "A__1",
717
+ "A__2",
718
+ ],
719
+ "numSamples": 1
720
+ },
721
+ {
722
+ "order": "fixed",
723
+ "components": [
724
+ "B__1",
725
+ "B__2"
726
+ ],
727
+ "numSamples": 1
728
+ },
729
+ ]
730
+
731
+ }
732
+ '''
733
+ ```
435
734
  ## Development
436
735
 
437
736
  We recommend using [uv](https://github.com/astral-sh/uv) for development.
@@ -1,10 +1,10 @@
1
1
  revisit/StudyConfigSchema.json,sha256=xtzwZifuPJoiHASx0o8PHqBuh5L30mjBlhQ5eqsYm10,132168
2
2
  revisit/__init__.py,sha256=QCvYt8m9QwpjcK4dv6GlLMUDCzRXGy16cua1r2biNCg,255
3
3
  revisit/models.py,sha256=FRy8IlUAtDS3gdmZwwvqR935lbViTPnnr7k0CAkDkzI,134084
4
- revisit/revisit.py,sha256=3owakIURK3BUtVnKx0l9UD3nTUqy9JdT-QR21hKxNM4,27772
4
+ revisit/revisit.py,sha256=cLCXFrUP0H7IXAsZWFegO6EJBIXuBPl-NKl68-3Fem4,29532
5
5
  revisit/widget.py,sha256=VvFqRvvvn86fW8ASe1pxaAvh5ZLvvSRThI5XtlCdgcg,915
6
6
  revisit/static/widget.css,sha256=TLu5F6k0CvowQtmApPswG-JZUXYszo7a10dVWKnZsIg,647
7
7
  revisit/static/widget.js,sha256=jjc-RvauEnU8dHCR7oGSjg0y1CRXtAlw1-4yDeSbdKE,186472
8
- revisit-0.0.26.dist-info/METADATA,sha256=YNlwwgHdZFF4gtWiloQn6MIPz6y9FAr4jmlra9jH5rQ,13510
9
- revisit-0.0.26.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
10
- revisit-0.0.26.dist-info/RECORD,,
8
+ revisit-0.0.28.dist-info/METADATA,sha256=dwgAyxwbnoNV6YIq8pfjbRG9VRGM9PLfHSv1OIymKkk,24407
9
+ revisit-0.0.28.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
10
+ revisit-0.0.28.dist-info/RECORD,,