surveyjs 0.1.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.
Files changed (60) hide show
  1. surveyjs-0.1.0/LICENSE +21 -0
  2. surveyjs-0.1.0/PKG-INFO +137 -0
  3. surveyjs-0.1.0/README.md +118 -0
  4. surveyjs-0.1.0/pyproject.toml +33 -0
  5. surveyjs-0.1.0/setup.cfg +4 -0
  6. surveyjs-0.1.0/surveyjs/__init__.py +21 -0
  7. surveyjs-0.1.0/surveyjs/creator.py +165 -0
  8. surveyjs-0.1.0/surveyjs/form.py +147 -0
  9. surveyjs-0.1.0/surveyjs/questions/__init__.py +2 -0
  10. surveyjs-0.1.0/surveyjs/questions/boolean.py +46 -0
  11. surveyjs-0.1.0/surveyjs/questions/checkbox.py +65 -0
  12. surveyjs-0.1.0/surveyjs/questions/comment.py +25 -0
  13. surveyjs-0.1.0/surveyjs/questions/dropdown.py +59 -0
  14. surveyjs-0.1.0/surveyjs/questions/expression.py +49 -0
  15. surveyjs-0.1.0/surveyjs/questions/file.py +53 -0
  16. surveyjs-0.1.0/surveyjs/questions/html.py +21 -0
  17. surveyjs-0.1.0/surveyjs/questions/image.py +43 -0
  18. surveyjs-0.1.0/surveyjs/questions/imagepicker.py +40 -0
  19. surveyjs-0.1.0/surveyjs/questions/matrix.py +61 -0
  20. surveyjs-0.1.0/surveyjs/questions/matrixdropdown.py +55 -0
  21. surveyjs-0.1.0/surveyjs/questions/matrixdynamic.py +66 -0
  22. surveyjs-0.1.0/surveyjs/questions/multipletext.py +44 -0
  23. surveyjs-0.1.0/surveyjs/questions/nonvalue.py +77 -0
  24. surveyjs-0.1.0/surveyjs/questions/panel.py +49 -0
  25. surveyjs-0.1.0/surveyjs/questions/paneldynamic.py +78 -0
  26. surveyjs-0.1.0/surveyjs/questions/question.py +208 -0
  27. surveyjs-0.1.0/surveyjs/questions/radiogroup.py +58 -0
  28. surveyjs-0.1.0/surveyjs/questions/ranking.py +36 -0
  29. surveyjs-0.1.0/surveyjs/questions/rating.py +65 -0
  30. surveyjs-0.1.0/surveyjs/questions/signaturepad.py +44 -0
  31. surveyjs-0.1.0/surveyjs/questions/tagbox.py +52 -0
  32. surveyjs-0.1.0/surveyjs/questions/text.py +77 -0
  33. surveyjs-0.1.0/surveyjs.egg-info/PKG-INFO +137 -0
  34. surveyjs-0.1.0/surveyjs.egg-info/SOURCES.txt +58 -0
  35. surveyjs-0.1.0/surveyjs.egg-info/dependency_links.txt +1 -0
  36. surveyjs-0.1.0/surveyjs.egg-info/top_level.txt +1 -0
  37. surveyjs-0.1.0/tests/test_form.py +97 -0
  38. surveyjs-0.1.0/tests/test_nested_questions.py +17 -0
  39. surveyjs-0.1.0/tests/test_question_boolean.py +57 -0
  40. surveyjs-0.1.0/tests/test_question_checkbox.py +79 -0
  41. surveyjs-0.1.0/tests/test_question_comment.py +60 -0
  42. surveyjs-0.1.0/tests/test_question_dropdown.py +60 -0
  43. surveyjs-0.1.0/tests/test_question_expression.py +48 -0
  44. surveyjs-0.1.0/tests/test_question_file.py +69 -0
  45. surveyjs-0.1.0/tests/test_question_html.py +48 -0
  46. surveyjs-0.1.0/tests/test_question_image.py +62 -0
  47. surveyjs-0.1.0/tests/test_question_imagepicker.py +61 -0
  48. surveyjs-0.1.0/tests/test_question_matrix.py +69 -0
  49. surveyjs-0.1.0/tests/test_question_matrixdropdown.py +72 -0
  50. surveyjs-0.1.0/tests/test_question_matrixdynamic.py +75 -0
  51. surveyjs-0.1.0/tests/test_question_multipletext.py +68 -0
  52. surveyjs-0.1.0/tests/test_question_panel.py +68 -0
  53. surveyjs-0.1.0/tests/test_question_paneldynamic.py +95 -0
  54. surveyjs-0.1.0/tests/test_question_radiogroup.py +66 -0
  55. surveyjs-0.1.0/tests/test_question_ranking.py +66 -0
  56. surveyjs-0.1.0/tests/test_question_rating.py +63 -0
  57. surveyjs-0.1.0/tests/test_question_signaturepad.py +60 -0
  58. surveyjs-0.1.0/tests/test_question_tagbox.py +69 -0
  59. surveyjs-0.1.0/tests/test_question_text.py +123 -0
  60. surveyjs-0.1.0/tests/test_survey.py +88 -0
surveyjs-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nova Code (https://www.novacode.nl)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,137 @@
1
+ Metadata-Version: 2.4
2
+ Name: surveyjs
3
+ Version: 0.1.0
4
+ Summary: SurveyJS (JSON data) API for Python
5
+ Author-email: Bob Leers <bob@novacode.nl>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/novacode-nl/python-surveyjs
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
13
+ Classifier: Topic :: File Formats :: JSON
14
+ Classifier: Topic :: File Formats :: JSON :: JSON Schema
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Dynamic: license-file
19
+
20
+ # surveyjs (Python)
21
+
22
+ SurveyJS (JSON Form Builder) data API for Python.
23
+
24
+ For information about the SurveyJS project, see https://surveyjs.io
25
+
26
+ ## Introduction
27
+
28
+ **surveyjs** is a Python package which loads and transforms
29
+ SurveyJS **Creator JSON** (survey schema) and **Form JSON** (submission data)
30
+ into **usable Python objects**.
31
+
32
+ Its main aim is to provide easy access to a SurveyJS Form's questions (fields, layout elements, etc.)
33
+ also captured as **Python objects**, which makes this API very versatile and usable.
34
+
35
+ **Notes about terms:**
36
+ - **SurveyCreator:** The Survey Creator (form builder) schema which is the design of a Form.
37
+ - **SurveyForm:** A filled-in Survey Form, aka Form response, submission.
38
+ - **Question:** Input (field) and layout elements in SurveyJS (Creator and Form). Question is not a semantic term for a layout element (e.g. panel), but we follow the SurveyJS convention of calling all components "questions".
39
+
40
+ **Question types:**
41
+ Source code (file prefix `question`): https://github.com/surveyjs/survey-library/tree/master/packages/survey-core/src
42
+
43
+ ## Features
44
+
45
+ - Compatible with Python 3.8 and later
46
+ - Constructor of the **SurveyCreator** and **SurveyForm** class only requires
47
+ the JSON (string or dict).
48
+ - Get a SurveyForm object's Questions as usable Python objects
49
+ e.g. datetime, boolean, list (for checkbox), dict (for matrix) etc.
50
+ - Support for all SurveyJS question types
51
+ - Open source (MIT License)
52
+
53
+ ## Installation
54
+
55
+ ```
56
+ pip install surveyjs
57
+ ```
58
+
59
+ ### Source Install
60
+
61
+ ```
62
+ git clone <repo-url>
63
+ cd python-surveyjs
64
+ pip install -e .
65
+ ```
66
+
67
+ ## Usage Examples
68
+
69
+ ```python
70
+ from surveyjs import SurveyCreator, SurveyForm
71
+
72
+ # survey_json is a SurveyJS Creator JSON schema (string or dict)
73
+ # form_json is a SurveyJS Form submission JSON (string or dict)
74
+
75
+ creator = SurveyCreator(survey_json)
76
+ form = SurveyForm(form_json, creator)
77
+
78
+ # Text question
79
+ print(form.input_questions['firstName'].label)
80
+ # 'First Name'
81
+
82
+ print(form.input_questions['firstName'].value)
83
+ # 'Bob'
84
+
85
+ # Checkbox question
86
+ print(form.input_questions['colors'].value)
87
+ # ['red', 'blue']
88
+
89
+ # Rating question
90
+ print(form.input_questions['satisfaction'].value)
91
+ # 4
92
+
93
+ # Boolean question
94
+ print(form.input_questions['agree'].value)
95
+ # True
96
+
97
+ # Matrix question
98
+ print(form.input_questions['quality'].value)
99
+ # {'affordable': 'good', 'does-what-it-claims': 'excellent'}
100
+
101
+ # Access by attribute
102
+ print(form.data.firstName.value)
103
+ # 'Bob'
104
+ ```
105
+
106
+ ## Unit Tests
107
+
108
+ ### Run all tests
109
+
110
+ From toplevel directory:
111
+ ```
112
+ poetry run python -m unittest
113
+ ```
114
+
115
+ ### Run specific (questions) unittests
116
+
117
+ All questions, from toplevel directory:
118
+
119
+ ```
120
+ poetry run python -m unittest tests/test_question_*.py
121
+ ```
122
+
123
+ Nested questions (complexity), from toplevel directory:
124
+
125
+ ```
126
+ poetry run python -m unittest tests/test_nested_questions.py
127
+ ```
128
+
129
+ ### Run specific component unittest
130
+
131
+ ```
132
+ poetry run python -m unittest tests.test_question_ranking.TestQuestionRanking.test_choices
133
+ ```
134
+
135
+ ## License
136
+
137
+ [MIT](LICENSE)
@@ -0,0 +1,118 @@
1
+ # surveyjs (Python)
2
+
3
+ SurveyJS (JSON Form Builder) data API for Python.
4
+
5
+ For information about the SurveyJS project, see https://surveyjs.io
6
+
7
+ ## Introduction
8
+
9
+ **surveyjs** is a Python package which loads and transforms
10
+ SurveyJS **Creator JSON** (survey schema) and **Form JSON** (submission data)
11
+ into **usable Python objects**.
12
+
13
+ Its main aim is to provide easy access to a SurveyJS Form's questions (fields, layout elements, etc.)
14
+ also captured as **Python objects**, which makes this API very versatile and usable.
15
+
16
+ **Notes about terms:**
17
+ - **SurveyCreator:** The Survey Creator (form builder) schema which is the design of a Form.
18
+ - **SurveyForm:** A filled-in Survey Form, aka Form response, submission.
19
+ - **Question:** Input (field) and layout elements in SurveyJS (Creator and Form). Question is not a semantic term for a layout element (e.g. panel), but we follow the SurveyJS convention of calling all components "questions".
20
+
21
+ **Question types:**
22
+ Source code (file prefix `question`): https://github.com/surveyjs/survey-library/tree/master/packages/survey-core/src
23
+
24
+ ## Features
25
+
26
+ - Compatible with Python 3.8 and later
27
+ - Constructor of the **SurveyCreator** and **SurveyForm** class only requires
28
+ the JSON (string or dict).
29
+ - Get a SurveyForm object's Questions as usable Python objects
30
+ e.g. datetime, boolean, list (for checkbox), dict (for matrix) etc.
31
+ - Support for all SurveyJS question types
32
+ - Open source (MIT License)
33
+
34
+ ## Installation
35
+
36
+ ```
37
+ pip install surveyjs
38
+ ```
39
+
40
+ ### Source Install
41
+
42
+ ```
43
+ git clone <repo-url>
44
+ cd python-surveyjs
45
+ pip install -e .
46
+ ```
47
+
48
+ ## Usage Examples
49
+
50
+ ```python
51
+ from surveyjs import SurveyCreator, SurveyForm
52
+
53
+ # survey_json is a SurveyJS Creator JSON schema (string or dict)
54
+ # form_json is a SurveyJS Form submission JSON (string or dict)
55
+
56
+ creator = SurveyCreator(survey_json)
57
+ form = SurveyForm(form_json, creator)
58
+
59
+ # Text question
60
+ print(form.input_questions['firstName'].label)
61
+ # 'First Name'
62
+
63
+ print(form.input_questions['firstName'].value)
64
+ # 'Bob'
65
+
66
+ # Checkbox question
67
+ print(form.input_questions['colors'].value)
68
+ # ['red', 'blue']
69
+
70
+ # Rating question
71
+ print(form.input_questions['satisfaction'].value)
72
+ # 4
73
+
74
+ # Boolean question
75
+ print(form.input_questions['agree'].value)
76
+ # True
77
+
78
+ # Matrix question
79
+ print(form.input_questions['quality'].value)
80
+ # {'affordable': 'good', 'does-what-it-claims': 'excellent'}
81
+
82
+ # Access by attribute
83
+ print(form.data.firstName.value)
84
+ # 'Bob'
85
+ ```
86
+
87
+ ## Unit Tests
88
+
89
+ ### Run all tests
90
+
91
+ From toplevel directory:
92
+ ```
93
+ poetry run python -m unittest
94
+ ```
95
+
96
+ ### Run specific (questions) unittests
97
+
98
+ All questions, from toplevel directory:
99
+
100
+ ```
101
+ poetry run python -m unittest tests/test_question_*.py
102
+ ```
103
+
104
+ Nested questions (complexity), from toplevel directory:
105
+
106
+ ```
107
+ poetry run python -m unittest tests/test_nested_questions.py
108
+ ```
109
+
110
+ ### Run specific component unittest
111
+
112
+ ```
113
+ poetry run python -m unittest tests.test_question_ranking.TestQuestionRanking.test_choices
114
+ ```
115
+
116
+ ## License
117
+
118
+ [MIT](LICENSE)
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "surveyjs"
7
+ version = "0.1.0"
8
+ description = "SurveyJS (JSON data) API for Python"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.8"
12
+ authors = [
13
+ {name = "Bob Leers", email = "bob@novacode.nl"}
14
+ ]
15
+ dependencies = []
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Topic :: Software Development :: Libraries :: Python Modules",
22
+ "Topic :: File Formats :: JSON",
23
+ "Topic :: File Formats :: JSON :: JSON Schema"
24
+ ]
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/novacode-nl/python-surveyjs"
28
+
29
+ [tool.setuptools.packages.find]
30
+ include = ["surveyjs*"]
31
+
32
+ [tool.pytest.ini_options]
33
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,21 @@
1
+ # Copyright 2026 Nova Code (https://www.novacode.nl)
2
+ """
3
+ SurveyJS Python Package
4
+
5
+ This module serves as the entry point for the surveyjs package, providing
6
+ access to the core components for working with SurveyJS forms and the
7
+ survey creator.
8
+
9
+ It imports and exposes the following classes:
10
+ - SurveyForm: Handles the rendering and processing of SurveyJS forms.
11
+ - SurveyCreator: Provides functionality for creating and managing surveys.
12
+
13
+ Copyright 2026 Nova Code (https://www.novacode.nl)
14
+ See LICENSE file for full licensing details.
15
+ """
16
+ # See LICENSE file for full licensing details.
17
+
18
+ from surveyjs.creator import SurveyCreator
19
+ from surveyjs.form import SurveyForm
20
+
21
+ __all__ = ['SurveyCreator', 'SurveyForm']
@@ -0,0 +1,165 @@
1
+ # Copyright 2026 Nova Code (https://www.novacode.nl)
2
+ # See LICENSE file for full licensing details.
3
+
4
+ import json
5
+ import logging
6
+ from collections import OrderedDict
7
+ from copy import deepcopy
8
+
9
+ from surveyjs.questions.question import Question
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class SurveyCreator:
15
+ """
16
+ Represents a SurveyJS Creator schema (the survey blueprint/design).
17
+
18
+ Parses the survey JSON schema and creates Question objects for each
19
+ question element found in the schema.
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ schema_json,
25
+ language='en',
26
+ i18n=None,
27
+ question_class_mapping={},
28
+ **kwargs
29
+ ):
30
+ """
31
+ @param schema_json: SurveyJS Creator JSON schema (str or dict)
32
+ @param language: Language code for translations (default: 'en')
33
+ @param i18n: Translations dict
34
+ """
35
+ if isinstance(schema_json, dict):
36
+ self.schema = schema_json
37
+ else:
38
+ self.schema = json.loads(schema_json)
39
+
40
+ self.language = language
41
+ self.i18n = i18n or {}
42
+
43
+ self.question_class_mapping = question_class_mapping
44
+
45
+ # All questions (input + layout) keyed by name
46
+ self.questions = OrderedDict()
47
+
48
+ # Input-only questions keyed by name (no panels, html, image)
49
+ self.input_questions = OrderedDict()
50
+
51
+ # Questions keyed by internal id
52
+ self.question_ids = OrderedDict()
53
+
54
+ # Load all questions from the schema
55
+ self.load_questions()
56
+
57
+ @property
58
+ def title(self):
59
+ return self.schema.get('title', '')
60
+
61
+ @property
62
+ def description(self):
63
+ return self.schema.get('description', '')
64
+
65
+ @property
66
+ def pages(self):
67
+ return self.schema.get('pages', [])
68
+
69
+ @property
70
+ def components(self):
71
+ return self.questions
72
+
73
+ @property
74
+ def input_components(self):
75
+ return self.input_questions
76
+
77
+ def load_questions(self):
78
+ """Load questions from the schema, handling both pages-based and
79
+ flat elements-based schemas."""
80
+ pages = self.schema.get('pages')
81
+ if pages:
82
+ for page in pages:
83
+ elements = page.get('elements', [])
84
+ self._load_elements(elements)
85
+ else:
86
+ # Single page: elements at top level
87
+ elements = self.schema.get('elements', [])
88
+ self._load_elements(elements)
89
+
90
+ def _load_elements(self, elements, parent=None):
91
+ """Recursively load elements from the schema."""
92
+ for element in elements:
93
+ if 'type' not in element:
94
+ continue
95
+
96
+ question_obj = self.get_question_object(element)
97
+ if not question_obj:
98
+ continue
99
+
100
+ question_obj.load(
101
+ question_owner=self,
102
+ parent=parent,
103
+ data=None,
104
+ is_form=False,
105
+ )
106
+ self.questions[question_obj.name] = question_obj
107
+
108
+ if question_obj.id:
109
+ self.question_ids[question_obj.id] = question_obj
110
+
111
+ # Recurse into panel/page elements
112
+ if element['type'] in ('panel', 'paneldynamic'):
113
+ nested = element.get('elements', [])
114
+ self._load_elements(nested, parent=question_obj)
115
+
116
+ def get_question_class(self, element):
117
+ """Dynamically load the question class based on the element type."""
118
+ element_type = element.get('type')
119
+ element_type_cap = element_type.capitalize() if element_type else None
120
+ if not element_type:
121
+ return None
122
+
123
+ cls_mapping = self.question_class_mapping.get(element_type)
124
+ if cls_mapping:
125
+ cls_mapping = cls_mapping.capitalize() if isinstance(cls_mapping, str) else cls_mapping
126
+ if isinstance(cls_mapping, str):
127
+ cls_name = f"Question{cls_mapping}"
128
+ import_path = f"surveyjs.questions.{element_type}.{cls_mapping}"
129
+ try:
130
+ module = __import__(import_path, fromlist=[cls_name])
131
+ cls = getattr(module, cls_name)
132
+ return cls
133
+ except (AttributeError, ModuleNotFoundError) as e:
134
+ logger.warning(
135
+ "Could not load question class for type '%s': %s. "
136
+ "Falling back to base Question.", element_type, e
137
+ )
138
+ return Question
139
+ else:
140
+ return cls_mapping
141
+ else:
142
+ cls_name = f"Question{element_type_cap}"
143
+ import_path = 'surveyjs.questions.%s' % element_type
144
+ try:
145
+ module = __import__(import_path, fromlist=[cls_name])
146
+ return getattr(module, cls_name)
147
+ except (AttributeError, ModuleNotFoundError) as e:
148
+ logger.warning(
149
+ "Could not load question class for type '%s': %s. "
150
+ "Falling back to base Question.", element_type, e
151
+ )
152
+ return Question
153
+
154
+ def get_question_object(self, element):
155
+ """Create a question object from an element dict."""
156
+ cls = self.get_question_class(element)
157
+ if cls is None:
158
+ return None
159
+ return cls(element, self, language=self.language, i18n=self.i18n)
160
+
161
+ @property
162
+ def form(self):
163
+ """Placeholder form dict (always empty). Useful in contexts where
164
+ a question owner's form is requested."""
165
+ return {}
@@ -0,0 +1,147 @@
1
+ # Copyright 2026 Nova Code (https://www.novacode.nl)
2
+ # See LICENSE file for full licensing details.
3
+
4
+ import json
5
+ import logging
6
+ from collections import OrderedDict
7
+
8
+ from surveyjs import SurveyCreator
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class SurveyForm:
14
+ """
15
+ Represents a filled-in SurveyJS form (submission data).
16
+
17
+ Parses the form submission JSON against a SurveyCreator schema and creates
18
+ Question objects with values populated from the submission.
19
+ """
20
+
21
+ def __init__(
22
+ self,
23
+ form_json,
24
+ creator=None,
25
+ creator_schema_json=None,
26
+ lang="en",
27
+ **kwargs
28
+ ):
29
+ """
30
+ @param form_json: SurveyJS Form submission JSON (str or dict)
31
+ @param creator: A SurveyCreator instance
32
+ @param creator_schema_json: SurveyCreator schema JSON (str or dict)
33
+ Alternative to providing a SurveyCreator instance.
34
+ @param lang: Language code for translations (default: 'en')
35
+ """
36
+
37
+ if creator and creator_schema_json:
38
+ raise Exception(
39
+ "Constructor accepts either creator or creator_schema_json, not both."
40
+ )
41
+
42
+ if isinstance(form_json, dict):
43
+ self.form = form_json
44
+ else:
45
+ self.form = json.loads(form_json)
46
+
47
+ self.creator = creator
48
+ self.creator_schema_json = creator_schema_json
49
+
50
+ if self.creator is None and self.creator_schema_json:
51
+ self.creator = SurveyCreator(self.creator_schema_json)
52
+
53
+ if self.creator:
54
+ if not isinstance(self.creator, SurveyCreator):
55
+ raise TypeError("creator must be a SurveyCreator instance")
56
+ elif self.creator_schema_json:
57
+ assert isinstance(self.creator_schema_json, str)
58
+
59
+ else:
60
+ raise Exception(
61
+ "Provide either the argument: creator or creator_schema_json."
62
+ )
63
+
64
+ self.lang = lang
65
+ # defaults to English (en) date/time format
66
+ self.date_format = kwargs.get('date_format', '%m/%d/%Y')
67
+ self.time_format = kwargs.get('time_format', '%H:%M:%S')
68
+
69
+ # All questions (input + layout) keyed by name
70
+ self.questions = OrderedDict()
71
+
72
+ # Input-only questions keyed by name
73
+ self.input_questions = OrderedDict()
74
+
75
+ # Questions keyed by id
76
+ self.question_ids = {}
77
+
78
+ # Load questions from survey schema + populate values from form data
79
+ self.load_questions()
80
+
81
+ # Create attribute-style accessor
82
+ self._data = FormData(self)
83
+
84
+ def set_creator_by_creator_schema_json(self):
85
+ self.creator = SurveyCreator(
86
+ self.creator_schema_json,
87
+ language=self.lang,
88
+ )
89
+
90
+ @property
91
+ def data(self):
92
+ """Attribute-style access to input questions."""
93
+ return self._data
94
+
95
+ @property
96
+ def components(self):
97
+ """ Alias for questions, including both input and layout questions."""
98
+ return self.questions
99
+
100
+ @property
101
+ def input_components(self):
102
+ """ Alias for questions, including input queastions."""
103
+ return self.input_questions
104
+
105
+ def load_questions(self):
106
+ """Load questions from the survey schema and populate values from
107
+ form submission data."""
108
+ for key, creator_question in self.creator.questions.items():
109
+ # Create a new question object (don't affect the Survey's question)
110
+ question_obj = self.creator.get_question_object(creator_question.raw)
111
+ question_obj.load(
112
+ question_owner=self,
113
+ parent=None,
114
+ data=self.form,
115
+ is_form=True,
116
+ )
117
+ self.questions[key] = question_obj
118
+
119
+ if question_obj.id:
120
+ self.question_ids[question_obj.id] = question_obj
121
+
122
+ def get_question_by_name(self, name):
123
+ """Get a question by its name."""
124
+ return self.input_questions.get(name)
125
+
126
+ def get_value(self, name):
127
+ """Get the value of a question by name."""
128
+ question = self.input_questions.get(name)
129
+ if question:
130
+ return question.value
131
+ return None
132
+
133
+
134
+ class FormData:
135
+ """Provides attribute-style access to form input questions.
136
+
137
+ Example:
138
+ form.data.firstName.value # => 'Bob'
139
+ """
140
+
141
+ def __init__(self, form):
142
+ self._form = form
143
+
144
+ def __getattr__(self, key):
145
+ if key.startswith('_'):
146
+ return super().__getattribute__(key)
147
+ return self._form.input_questions.get(key)
@@ -0,0 +1,2 @@
1
+ # Copyright 2026 Nova Code (https://www.novacode.nl)
2
+ # See LICENSE file for full licensing details.
@@ -0,0 +1,46 @@
1
+ # Copyright 2026 Nova Code (https://www.novacode.nl)
2
+ # See LICENSE file for full licensing details.
3
+
4
+ from .question import Question
5
+
6
+
7
+ class QuestionBoolean(Question):
8
+ """SurveyJS Boolean (Yes/No) question.
9
+
10
+ Value is typically True/False.
11
+ """
12
+
13
+ @property
14
+ def label_true(self):
15
+ return self.raw.get('labelTrue', 'Yes')
16
+
17
+ @property
18
+ def label_false(self):
19
+ return self.raw.get('labelFalse', 'No')
20
+
21
+ @property
22
+ def value_true(self):
23
+ return self.raw.get('valueTrue', True)
24
+
25
+ @property
26
+ def value_false(self):
27
+ return self.raw.get('valueFalse', False)
28
+
29
+ @property
30
+ def render_as(self):
31
+ """Render mode: 'default' (toggle) or 'checkbox' or 'radio'."""
32
+ return self.raw.get('renderAs', 'default')
33
+
34
+ def to_bool(self):
35
+ """Convert the value to a Python bool."""
36
+ val = self.value
37
+ if val is None:
38
+ return None
39
+ if isinstance(val, bool):
40
+ return val
41
+ if val == self.value_true:
42
+ return True
43
+ if val == self.value_false:
44
+ return False
45
+ # Fallback
46
+ return bool(val)