formelement 0.1.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.
@@ -0,0 +1,15 @@
1
+ """Public package interface for formelement."""
2
+
3
+ from .answer_patterns import AnswerPatternGenerator, dataGoogleForm
4
+ from .field_types import AnswerFieldType, dataPilihan
5
+ from .google_form_browser import GoogleFormBrowser
6
+ from .google_form_submit import submit_answers
7
+
8
+ __all__ = [
9
+ "AnswerFieldType",
10
+ "AnswerPatternGenerator",
11
+ "GoogleFormBrowser",
12
+ "dataGoogleForm",
13
+ "dataPilihan",
14
+ "submit_answers",
15
+ ]
@@ -0,0 +1,141 @@
1
+ import random
2
+
3
+
4
+ class AnswerPatternGenerator:
5
+ def hitungUsia(self, batasAwal: int, batasAkhir: int) -> int:
6
+ return random.randint(batasAwal, batasAkhir)
7
+
8
+ def pilihTipe(self, jenis: list[int]) -> int:
9
+ data: list[int] = []
10
+
11
+ for i in range(len(jenis)):
12
+ if jenis[i] != 0:
13
+ data.append(i)
14
+
15
+ return random.sample(data, 1)[0]
16
+
17
+ def pilihanCheckbox(self, awal: int, akhir: int, banyakData: int) -> list[int]:
18
+ temp = random.randint(1, banyakData)
19
+
20
+ listData: list[int] = []
21
+ i = awal
22
+ while i <= akhir:
23
+ listData.append(i)
24
+ i += 1
25
+
26
+ return random.sample(listData, temp)
27
+
28
+ def polaJawab1(
29
+ self,
30
+ pola: int,
31
+ awal: int,
32
+ banyakSoal: int,
33
+ kelipatan: int,
34
+ ) -> list[int]:
35
+ p = awal
36
+ hasil: list[int] = []
37
+ for _ in range(banyakSoal):
38
+ hasil.append(p)
39
+ p += kelipatan
40
+ return hasil
41
+
42
+ def polaJawab2(
43
+ self,
44
+ pola: int,
45
+ awal: int,
46
+ banyakSoal: int,
47
+ kelipatan: int,
48
+ ) -> list[int]:
49
+ p = awal
50
+ hasil: list[int] = []
51
+ for _ in range(banyakSoal):
52
+ if pola == 0:
53
+ s1 = random.choice([p, p + 1])
54
+ elif pola == 1:
55
+ s1 = random.choice([p, p + 1, p + 1])
56
+ elif pola == 2:
57
+ s1 = random.choice([p, p + 1, p + 1, p + 1])
58
+ elif pola == 3:
59
+ s1 = random.choice([p, p, p + 1])
60
+ elif pola == 4:
61
+ s1 = random.choice([p, p, p, p + 1])
62
+ else:
63
+ s1 = random.choice([p, p + 1])
64
+
65
+ hasil.append(s1)
66
+ return hasil
67
+
68
+ def polaJawab3(
69
+ self,
70
+ pola: int,
71
+ awal: int,
72
+ banyakSoal: int,
73
+ ) -> list[int]:
74
+ p = awal
75
+ hasil: list[int] = []
76
+ for _ in range(banyakSoal):
77
+ if pola == 0:
78
+ s1 = random.choice([p, p + 1, p + 2])
79
+ elif pola == 1:
80
+ s1 = random.choice([p, p + 1, p + 1, p + 2, p + 2])
81
+ elif pola == 2:
82
+ s1 = random.choice([p, p + 1, p + 1, p + 1, p + 2, p + 2, p + 2])
83
+ elif pola == 3:
84
+ s1 = random.choice(
85
+ [p, p + 1, p + 1, p + 1, p + 1, p + 2, p + 2, p + 2, p + 2]
86
+ )
87
+ elif pola == 4:
88
+ s1 = random.choice([p, p, p + 1, p + 1, p + 2])
89
+ elif pola == 5:
90
+ s1 = random.choice([p, p, p, p + 1, p + 1, p + 1, p + 2])
91
+ elif pola == 6:
92
+ s1 = random.choice([p, p, p, p, p + 1, p + 1, p + 1, p + 1, p + 2])
93
+ elif pola == 7:
94
+ s1 = random.choice([p, p, p + 1, p + 1, p + 1, p + 2])
95
+ else:
96
+ s1 = random.choice([p, p + 1, p + 2])
97
+
98
+ hasil.append(s1)
99
+ return hasil
100
+
101
+ def polaJawab4(
102
+ self,
103
+ pola: int,
104
+ awal: int,
105
+ banyakSoal: int,
106
+ kelipatan: int,
107
+ ) -> list[int]:
108
+ p = awal
109
+ hasil: list[int] = []
110
+ for _ in range(banyakSoal):
111
+ if pola == 0:
112
+ s1 = random.choice([p, p + 1, p + 2, p + 3])
113
+ elif pola == 1:
114
+ s1 = random.choice([p, p + 1, p + 1, p + 2, p + 2, p + 3, p + 3])
115
+ elif pola == 2:
116
+ s1 = random.choice(
117
+ [p, p + 1, p + 1, p + 1, p + 2, p + 2, p + 2, p + 3, p + 3, p + 3]
118
+ )
119
+ else:
120
+ s1 = random.choice([p, p + 1, p + 2, p + 3])
121
+
122
+ hasil.append(s1)
123
+ p += kelipatan
124
+ return hasil
125
+
126
+ def polaJawabCustom(
127
+ self,
128
+ awal: int,
129
+ soal: list[int],
130
+ kelipatan: int,
131
+ ) -> list[int]:
132
+ p = awal
133
+ hasil: list[int] = []
134
+ for i in range(len(soal)):
135
+ s1 = soal[i] + p
136
+ hasil.append(s1)
137
+ p += kelipatan
138
+ return hasil
139
+
140
+
141
+ dataGoogleForm = AnswerPatternGenerator
@@ -0,0 +1,15 @@
1
+ from enum import Enum
2
+
3
+
4
+ class AnswerFieldType(Enum):
5
+ ISIANPENDEK = 0
6
+ ISIANPANJANG = 1
7
+ KEBAWAH = 2
8
+ KESAMPING = 3
9
+ CHECKBOX = 4
10
+ LAINNYA = 5
11
+ DROPDOWN = 6
12
+ BUBBLE = 7
13
+
14
+
15
+ dataPilihan = AnswerFieldType
@@ -0,0 +1,152 @@
1
+ import random
2
+ import time
3
+
4
+ from selenium import webdriver
5
+ from selenium.webdriver.common.by import By
6
+ from selenium.webdriver.remote.webelement import WebElement
7
+
8
+ from .field_types import AnswerFieldType as pil
9
+
10
+
11
+ class GoogleFormBrowser:
12
+ driver: webdriver.Firefox
13
+
14
+ def __init__(self, driver: webdriver.Firefox):
15
+ self.driver = driver
16
+
17
+ def pindahHalaman(self):
18
+ time.sleep(3)
19
+ submit_button = self.driver.find_elements(By.XPATH, "//span[contains(text(), 'Berikutnya')]")
20
+ submit_button[0].click()
21
+
22
+ def kirim(self):
23
+ time.sleep(3)
24
+ submit_button = self.driver.find_elements(By.XPATH, "//span[contains(text(), 'Kirim')]")
25
+ submit_button[0].click()
26
+
27
+ def kirimJawaban(self):
28
+ time.sleep(2)
29
+ submit_button = self.driver.find_elements(By.XPATH, "//a[contains(text(), 'Kirim')]")
30
+ submit_button[0].click()
31
+
32
+ def tombol(self, pilihan: pil) -> list[WebElement]:
33
+ if pilihan == pil.ISIANPENDEK:
34
+ return self.driver.find_elements("css selector", ".whsOnd")
35
+ if pilihan == pil.ISIANPANJANG:
36
+ return self.driver.find_elements("css selector", ".KHxj8b")
37
+ if pilihan == pil.KEBAWAH:
38
+ return self.driver.find_elements("css selector", ".nWQGrd")
39
+ if pilihan == pil.KESAMPING:
40
+ return self.driver.find_elements("css selector", ".T5pZmf")
41
+ if pilihan == pil.CHECKBOX:
42
+ return self.driver.find_elements("css selector", ".uHMk6b")
43
+ if pilihan == pil.LAINNYA:
44
+ return self.driver.find_elements("css selector", ".Hvn9fb")
45
+ if pilihan == pil.DROPDOWN:
46
+ return self.driver.find_elements("css selector", ".ry3kXd")
47
+ if pilihan == pil.BUBBLE:
48
+ return self.driver.find_elements("css selector", ".AB7Lab")
49
+ return []
50
+
51
+ def pilihDropdown(self, pilihan: str):
52
+ time.sleep(2)
53
+ option = self.driver.find_elements(By.XPATH, f"//span[contains(text(), '{pilihan}')]")
54
+ option[len(option) - 1].click()
55
+
56
+ def hitungUsia(self, batasAwal: int, batasAkhir: int) -> int:
57
+ return random.randint(batasAwal, batasAkhir)
58
+
59
+ def pilihTipe(self, jenis: list[int]) -> int:
60
+ data: list[int] = []
61
+
62
+ for i in range(len(jenis)):
63
+ if jenis[i] != 0:
64
+ data.append(i)
65
+
66
+ return random.sample(data, 1)[0]
67
+
68
+ def pilihanCheckbox(self, awal: int, akhir: int, banyakData: int) -> list[int]:
69
+ temp = random.randint(1, banyakData)
70
+
71
+ listData: list[int] = []
72
+ i = awal
73
+ while i <= akhir:
74
+ listData.append(i)
75
+ i += 1
76
+
77
+ return random.sample(listData, temp)
78
+
79
+ def polaJawab1(self, pola: int, awal: int, banyakSoal: int, kelipatan: int, jawab: list[WebElement]):
80
+ p = awal
81
+ for _ in range(banyakSoal):
82
+ jawab[p].click()
83
+ p += kelipatan
84
+
85
+ def polaJawab2(self, pola: int, awal: int, banyakSoal: int, kelipatan: int, jawab: list[WebElement]):
86
+ p = awal
87
+ for _ in range(banyakSoal):
88
+ if pola == 0:
89
+ s1 = random.choice([p, p + 1])
90
+ elif pola == 1:
91
+ s1 = random.choice([p, p + 1, p + 1])
92
+ elif pola == 2:
93
+ s1 = random.choice([p, p + 1, p + 1, p + 1])
94
+ elif pola == 3:
95
+ s1 = random.choice([p, p, p + 1])
96
+ elif pola == 4:
97
+ s1 = random.choice([p, p, p, p + 1])
98
+ else:
99
+ s1 = random.choice([p, p + 1])
100
+
101
+ jawab[s1].click()
102
+ p += kelipatan
103
+
104
+ def polaJawab3(self, pola: int, awal: int, banyakSoal: int, kelipatan: int, jawab: list[WebElement]):
105
+ p = awal
106
+ for _ in range(banyakSoal):
107
+ if pola == 0:
108
+ s1 = random.choice([p, p + 1, p + 2])
109
+ elif pola == 1:
110
+ s1 = random.choice([p, p + 1, p + 1, p + 2, p + 2])
111
+ elif pola == 2:
112
+ s1 = random.choice([p, p + 1, p + 1, p + 1, p + 2, p + 2, p + 2])
113
+ elif pola == 3:
114
+ s1 = random.choice([p, p + 1, p + 1, p + 1, p + 1, p + 2, p + 2, p + 2, p + 2])
115
+ elif pola == 4:
116
+ s1 = random.choice([p, p, p + 1, p + 1, p + 2])
117
+ elif pola == 5:
118
+ s1 = random.choice([p, p, p, p + 1, p + 1, p + 1, p + 2])
119
+ elif pola == 6:
120
+ s1 = random.choice([p, p, p, p, p + 1, p + 1, p + 1, p + 1, p + 2])
121
+ elif pola == 7:
122
+ s1 = random.choice([p, p, p + 1, p + 1, p + 1, p + 2])
123
+ else:
124
+ s1 = random.choice([p, p + 1, p + 2])
125
+
126
+ jawab[s1].click()
127
+ p += kelipatan
128
+
129
+ def polaJawab4(self, pola: int, awal: int, banyakSoal: int, kelipatan: int, jawab: list[WebElement]):
130
+ p = awal
131
+ for _ in range(banyakSoal):
132
+ if pola == 0:
133
+ s1 = random.choice([p, p + 1, p + 2, p + 3])
134
+ elif pola == 1:
135
+ s1 = random.choice([p, p + 1, p + 1, p + 2, p + 2, p + 3, p + 3])
136
+ elif pola == 2:
137
+ s1 = random.choice([p, p + 1, p + 1, p + 1, p + 2, p + 2, p + 2, p + 3, p + 3, p + 3])
138
+ else:
139
+ s1 = random.choice([p, p + 1, p + 2, p + 3])
140
+
141
+ jawab[s1].click()
142
+ p += kelipatan
143
+
144
+ def polaJawabCustom(self, awal: int, soal: list[int], kelipatan: int, jawab: list[WebElement]):
145
+ p = awal
146
+ for i in range(len(soal)):
147
+ s1 = soal[i] + p
148
+ jawab[s1].click()
149
+ p += kelipatan
150
+
151
+
152
+ dataGoogleForm = GoogleFormBrowser
@@ -0,0 +1,174 @@
1
+ import json
2
+ import time
3
+ from importlib import import_module
4
+ from typing import Dict, Iterable, List, Optional, Sequence, Tuple
5
+ from urllib.error import HTTPError, URLError
6
+ from urllib.parse import urlencode
7
+ from urllib.request import Request, urlopen
8
+
9
+ POST_URL_TEMPLATE = "https://docs.google.com/forms/d/e/{form_id}/formResponse"
10
+
11
+
12
+ def _load_data_module(module_name: str):
13
+ return import_module(module_name)
14
+
15
+
16
+ def _ensure_pages_aligned(
17
+ entry_ids: Sequence[Sequence[str]],
18
+ answers: Sequence[Sequence[str]],
19
+ ) -> None:
20
+ if len(entry_ids) != len(answers):
21
+ raise ValueError(
22
+ "Answer pages do not match FORM_ENTRY_IDS pages. "
23
+ f"Expected {len(entry_ids)}, got {len(answers)}."
24
+ )
25
+
26
+ for index, (ids, values) in enumerate(zip(entry_ids, answers)):
27
+ if len(ids) != len(values):
28
+ raise ValueError(
29
+ f"Answer count on page {index + 1} mismatches FORM_ENTRY_IDS "
30
+ f"(expected {len(ids)}, got {len(values)})."
31
+ )
32
+
33
+
34
+ def _compute_page_history(total_pages: int) -> str:
35
+ if total_pages <= 0:
36
+ return "0"
37
+ return ",".join(str(i) for i in range(total_pages))
38
+
39
+
40
+ def _extract_entry_token(entry_id: str) -> int:
41
+ try:
42
+ return int(entry_id.split(".", 1)[1])
43
+ except (IndexError, ValueError) as error:
44
+ raise ValueError(f"Invalid entry id format: {entry_id}") from error
45
+
46
+
47
+ def _build_partial_response(
48
+ entry_pages: Sequence[Sequence[str]],
49
+ answer_pages: Sequence[Sequence[str]],
50
+ session_state: str,
51
+ ) -> Optional[str]:
52
+ entries: List[List[object]] = []
53
+ for ids, values in zip(entry_pages, answer_pages):
54
+ for entry_id, answer in zip(ids, values):
55
+ entries.append(
56
+ [
57
+ None,
58
+ _extract_entry_token(entry_id),
59
+ [answer],
60
+ 0,
61
+ ]
62
+ )
63
+
64
+ if not entries:
65
+ return None
66
+
67
+ payload: List[object] = [entries, None]
68
+ if session_state:
69
+ payload.append(session_state)
70
+
71
+ return json.dumps(payload, separators=(",", ":"))
72
+
73
+
74
+ def _build_payload(
75
+ entry_ids: Sequence[Sequence[str]],
76
+ answers: Sequence[Sequence[str]],
77
+ session_state: str,
78
+ fbzx: Optional[str] = None,
79
+ ) -> Dict[str, str]:
80
+ total_pages = len(entry_ids)
81
+ last_page_ids = entry_ids[-1] if entry_ids else []
82
+ last_page_answers = answers[-1] if answers else []
83
+ timestamp_ms = str(int(time.time() * 1000))
84
+
85
+ payload: Dict[str, str] = {
86
+ entry_id: answer
87
+ for entry_id, answer in zip(last_page_ids, last_page_answers)
88
+ }
89
+
90
+ for entry_id in last_page_ids:
91
+ payload[f"{entry_id}_sentinel"] = ""
92
+
93
+ payload["dlut"] = timestamp_ms
94
+
95
+ partial = _build_partial_response(
96
+ entry_ids[:-1],
97
+ answers[:-1],
98
+ session_state,
99
+ )
100
+ if partial is not None:
101
+ payload["partialResponse"] = partial
102
+
103
+ payload["pageHistory"] = _compute_page_history(total_pages)
104
+ payload["fvv"] = "1"
105
+ if fbzx:
106
+ payload["fbzx"] = fbzx
107
+ payload["submissionTimestamp"] = timestamp_ms
108
+
109
+ return payload
110
+
111
+
112
+ def submit_answers(
113
+ answers: Iterable[Iterable[str]],
114
+ *,
115
+ data_module: str = "2_data",
116
+ session_state: Optional[str] = None,
117
+ fbzx: Optional[str] = None,
118
+ dry_run: bool = False,
119
+ ) -> Tuple[Optional[int], Dict[str, str], str]:
120
+ """
121
+ Submit a multi-page Google Form response.
122
+
123
+ The referenced data module must expose:
124
+ - FORM_ID
125
+ - FORM_ENTRY_IDS
126
+ - optionally FORM_SESSION_STATE
127
+ - optionally FORM_FBZX
128
+ """
129
+
130
+ normalized_answers = [
131
+ [str(value) for value in page]
132
+ for page in answers
133
+ ]
134
+
135
+ module = _load_data_module(data_module)
136
+ form_id: str = getattr(module, "FORM_ID", "")
137
+ entry_ids: Sequence[Sequence[str]] = getattr(module, "FORM_ENTRY_IDS", [])
138
+ default_session_state: str = getattr(module, "FORM_SESSION_STATE", "")
139
+ default_fbzx: str = getattr(module, "FORM_FBZX", "")
140
+
141
+ if not form_id:
142
+ raise ValueError("FORM_ID is missing in the data module.")
143
+
144
+ entry_ids = [list(page) for page in entry_ids]
145
+ _ensure_pages_aligned(entry_ids, normalized_answers)
146
+
147
+ payload = _build_payload(
148
+ entry_ids,
149
+ normalized_answers,
150
+ session_state if session_state is not None else default_session_state,
151
+ fbzx if fbzx is not None else default_fbzx,
152
+ )
153
+
154
+ encoded = urlencode(payload)
155
+
156
+ if dry_run:
157
+ return None, payload, encoded
158
+
159
+ request = Request(
160
+ POST_URL_TEMPLATE.format(form_id=form_id),
161
+ data=encoded.encode("utf-8"),
162
+ headers={"Content-Type": "application/x-www-form-urlencoded"},
163
+ )
164
+
165
+ try:
166
+ with urlopen(request) as response:
167
+ response.read()
168
+ status = response.status
169
+ except HTTPError as error:
170
+ raise RuntimeError(f"HTTP {error.code}: {error.reason}") from error
171
+ except URLError as error:
172
+ raise RuntimeError(f"Request failed: {error.reason}") from error
173
+
174
+ return status, payload, encoded
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Iqbal Rahman
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,95 @@
1
+ Metadata-Version: 2.1
2
+ Name: formelement
3
+ Version: 0.1.0
4
+ Summary: Form automation utilities with browser drivers such as GoogleFormBrowser.
5
+ Home-page: https://pypi.org/project/formelement/
6
+ Author: Iqbal Rahman
7
+ Author-email: iqbal@example.com
8
+ License: MIT
9
+ Platform: UNKNOWN
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: selenium (>=4.20)
14
+
15
+ # formelement
16
+
17
+ `formelement` is a Python package for form automation.
18
+
19
+ It currently provides:
20
+
21
+ - answer pattern helpers
22
+ - Google Form browser automation with Selenium
23
+ - Google Form HTTP submission helpers
24
+
25
+ The package is structured so you can later extend it with other drivers such as `MonkeyFormBrowser`.
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ pip install formelement
31
+ ```
32
+
33
+ `selenium` is installed automatically because browser automation is part of the core package.
34
+
35
+ ## Quick start
36
+
37
+ Generate answer patterns:
38
+
39
+ ```python
40
+ from formelement import AnswerPatternGenerator
41
+
42
+ generator = AnswerPatternGenerator()
43
+ answers = generator.polaJawab3(0, 1, 5)
44
+ print(answers)
45
+ ```
46
+
47
+ Use the browser driver:
48
+
49
+ ```python
50
+ from selenium import webdriver
51
+ from formelement import AnswerFieldType, GoogleFormBrowser
52
+
53
+ driver = webdriver.Firefox()
54
+ form = GoogleFormBrowser(driver)
55
+
56
+ short_inputs = form.tombol(AnswerFieldType.ISIANPENDEK)
57
+ ```
58
+
59
+ Submit form answers:
60
+
61
+ ```python
62
+ from formelement import submit_answers
63
+
64
+ status, payload, encoded = submit_answers(
65
+ [["Alice", "24"], ["Yes"]],
66
+ data_module="my_form_data",
67
+ dry_run=True,
68
+ )
69
+ ```
70
+
71
+ Your `data_module` must define:
72
+
73
+ ```python
74
+ FORM_ID = "your-form-id"
75
+ FORM_ENTRY_IDS = [
76
+ ["entry.123", "entry.456"],
77
+ ["entry.789"],
78
+ ]
79
+ FORM_SESSION_STATE = ""
80
+ FORM_FBZX = ""
81
+ ```
82
+
83
+ ## Public API
84
+
85
+ - `AnswerPatternGenerator`
86
+ - `AnswerFieldType`
87
+ - `GoogleFormBrowser`
88
+ - `submit_answers`
89
+
90
+ Legacy aliases from the original project are still available:
91
+
92
+ - `dataGoogleForm`
93
+ - `dataPilihan`
94
+
95
+
@@ -0,0 +1,10 @@
1
+ formelement/__init__.py,sha256=r3iZupdPfaaSaOwJwxmTlXH-X4mljYIvFe2BvbjZctw,425
2
+ formelement/answer_patterns.py,sha256=u1QO3W7vDw0vPdu15ctlEDr98pK2JHNqPm65kzVxOdc,4016
3
+ formelement/field_types.py,sha256=D38-l7WIQtqGWqq_w5sbMvRc9PdUBgjPF9IKhuuGci0,225
4
+ formelement/google_form_browser.py,sha256=1mqg-nbb0ihnt1LOggYy1ZlsNoqnq_mrh0xGsAur0cU,5583
5
+ formelement/google_form_submit.py,sha256=QItLcEa4MdMXnOb73ViflnO5O7xuwl9dlFnY5o4AAvE,5039
6
+ formelement-0.1.0.dist-info/LICENSE,sha256=tf1o27LAAwRgOQkajRdiMOmeSPI8vNKi6xgUzNU7pnY,1069
7
+ formelement-0.1.0.dist-info/METADATA,sha256=cf7pAdL8HCHpqMwnTciE2etjYqDexRvJpXLR9CuJELw,1882
8
+ formelement-0.1.0.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92
9
+ formelement-0.1.0.dist-info/top_level.txt,sha256=-zvOEToKwzJP1llc4a6iZXx8RfKktqoCswgtrF87VEI,12
10
+ formelement-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.37.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ formelement