eidosui 0.4.0__tar.gz → 0.6.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 (36) hide show
  1. eidosui-0.6.0/PKG-INFO +113 -0
  2. eidosui-0.6.0/README.md +77 -0
  3. eidosui-0.6.0/eidos/__init__.py +270 -0
  4. eidosui-0.6.0/eidos/components/__init__.py +11 -0
  5. {eidosui-0.4.0 → eidosui-0.6.0}/eidos/components/headers.py +16 -14
  6. eidosui-0.6.0/eidos/components/navigation.py +87 -0
  7. eidosui-0.6.0/eidos/components/table.py +84 -0
  8. eidosui-0.6.0/eidos/components/tabs.py +140 -0
  9. {eidosui-0.4.0 → eidosui-0.6.0}/eidos/css/styles.css +155 -0
  10. eidosui-0.6.0/eidos/plugins/__init__.py +1 -0
  11. {eidosui-0.4.0 → eidosui-0.6.0}/eidos/plugins/markdown/__init__.py +3 -3
  12. {eidosui-0.4.0 → eidosui-0.6.0}/eidos/plugins/markdown/components.py +10 -22
  13. eidosui-0.6.0/eidos/plugins/markdown/extensions/__init__.py +1 -0
  14. eidosui-0.6.0/eidos/plugins/markdown/extensions/alerts.py +124 -0
  15. {eidosui-0.4.0 → eidosui-0.6.0}/eidos/plugins/markdown/renderer.py +19 -24
  16. {eidosui-0.4.0 → eidosui-0.6.0}/eidos/styles.py +58 -11
  17. eidosui-0.6.0/eidos/tags.py +252 -0
  18. eidosui-0.6.0/eidos/utils.py +76 -0
  19. eidosui-0.6.0/pyproject.toml +163 -0
  20. eidosui-0.4.0/PKG-INFO +0 -127
  21. eidosui-0.4.0/README.md +0 -91
  22. eidosui-0.4.0/eidos/__init__.py +0 -0
  23. eidosui-0.4.0/eidos/components/navigation.py +0 -78
  24. eidosui-0.4.0/eidos/plugins/__init__.py +0 -1
  25. eidosui-0.4.0/eidos/plugins/markdown/extensions/__init__.py +0 -1
  26. eidosui-0.4.0/eidos/plugins/markdown/extensions/alerts.py +0 -134
  27. eidosui-0.4.0/eidos/tags.py +0 -194
  28. eidosui-0.4.0/eidos/utils.py +0 -72
  29. eidosui-0.4.0/pyproject.toml +0 -76
  30. {eidosui-0.4.0 → eidosui-0.6.0}/.gitignore +0 -0
  31. {eidosui-0.4.0 → eidosui-0.6.0}/LICENSE +0 -0
  32. {eidosui-0.4.0 → eidosui-0.6.0}/eidos/css/themes/dark.css +0 -0
  33. {eidosui-0.4.0 → eidosui-0.6.0}/eidos/css/themes/eidos-variables.css +0 -0
  34. {eidosui-0.4.0 → eidosui-0.6.0}/eidos/css/themes/light.css +0 -0
  35. {eidosui-0.4.0 → eidosui-0.6.0}/eidos/js/eidos.js +0 -0
  36. {eidosui-0.4.0 → eidosui-0.6.0}/eidos/plugins/markdown/css/markdown.css +0 -0
eidosui-0.6.0/PKG-INFO ADDED
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.4
2
+ Name: eidosui
3
+ Version: 0.6.0
4
+ Summary: A modern, Tailwind CSS-based UI library for air development
5
+ Project-URL: Homepage, https://github.com/isaac-flath/EidosUI
6
+ Project-URL: Repository, https://github.com/isaac-flath/EidosUI
7
+ Project-URL: Issues, https://github.com/isaac-flath/EidosUI/issues
8
+ Project-URL: Documentation, https://github.com/isaac-flath/EidosUI#readme
9
+ Author: Isaac Flath
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: air,components,css,fastapi,tailwind,ui,web
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: air>=0.12
25
+ Requires-Dist: fastapi[standard]
26
+ Requires-Dist: uvicorn
27
+ Provides-Extra: dev
28
+ Requires-Dist: black; extra == 'dev'
29
+ Requires-Dist: isort; extra == 'dev'
30
+ Requires-Dist: mypy; extra == 'dev'
31
+ Requires-Dist: pytest; extra == 'dev'
32
+ Requires-Dist: ruff; extra == 'dev'
33
+ Provides-Extra: markdown
34
+ Requires-Dist: markdown>=3.4; extra == 'markdown'
35
+ Description-Content-Type: text/markdown
36
+
37
+ # EidosUI
38
+
39
+ Modern UI library for Python web frameworks. Built on Air and Tailwind CSS.
40
+
41
+ > [!CAUTION]
42
+ > This library is in alpha, and may have semi-frequent breaking changes. I'd love for you to try it an contribute feedback or PRs!
43
+
44
+ ## Installation
45
+
46
+ ```bash
47
+ pip install eidosui
48
+ ```
49
+
50
+ ## Quick Start
51
+
52
+ ```python
53
+ from eidos import *
54
+ import air
55
+
56
+ app = air.Air()
57
+
58
+ @app.get("/")
59
+ def home():
60
+ return Html(
61
+ Head(
62
+ Title("My App"),
63
+ *EidosHeaders() # Required CSS/JS
64
+ ),
65
+ Body(
66
+ H1("Welcome"),
67
+ P("Build modern web apps with Python."),
68
+ DataTable.from_lists(
69
+ [["Alice", "30"], ["Bob", "25"]],
70
+ headers=["Name", "Age"]
71
+ )
72
+ )
73
+ )
74
+
75
+ app.run()
76
+ ```
77
+
78
+ ## Features
79
+
80
+ - **Styled HTML tags** - Pre-styled versions of all HTML elements
81
+ - **Components** - DataTable, NavBar, and more
82
+ - **Themes** - Light/dark themes via CSS variables
83
+ - **Type hints** - Full type annotations
84
+ - **Air integration** - Works seamlessly with Air framework
85
+
86
+ ## Plugins
87
+
88
+ ### Markdown
89
+
90
+ ```bash
91
+ pip install "eidosui[markdown]"
92
+ ```
93
+
94
+ ```python
95
+ from eidos.plugins.markdown import Markdown, MarkdownCSS
96
+
97
+ Head(
98
+ *EidosHeaders(),
99
+ MarkdownCSS() # Add markdown styles
100
+ )
101
+
102
+ Body(
103
+ Markdown("# Hello\n\nSupports **GitHub Flavored Markdown**")
104
+ )
105
+ ```
106
+
107
+ ## Documentation
108
+
109
+ Full documentation: https://eidosui.readthedocs.io
110
+
111
+ ## License
112
+
113
+ MIT
@@ -0,0 +1,77 @@
1
+ # EidosUI
2
+
3
+ Modern UI library for Python web frameworks. Built on Air and Tailwind CSS.
4
+
5
+ > [!CAUTION]
6
+ > This library is in alpha, and may have semi-frequent breaking changes. I'd love for you to try it an contribute feedback or PRs!
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ pip install eidosui
12
+ ```
13
+
14
+ ## Quick Start
15
+
16
+ ```python
17
+ from eidos import *
18
+ import air
19
+
20
+ app = air.Air()
21
+
22
+ @app.get("/")
23
+ def home():
24
+ return Html(
25
+ Head(
26
+ Title("My App"),
27
+ *EidosHeaders() # Required CSS/JS
28
+ ),
29
+ Body(
30
+ H1("Welcome"),
31
+ P("Build modern web apps with Python."),
32
+ DataTable.from_lists(
33
+ [["Alice", "30"], ["Bob", "25"]],
34
+ headers=["Name", "Age"]
35
+ )
36
+ )
37
+ )
38
+
39
+ app.run()
40
+ ```
41
+
42
+ ## Features
43
+
44
+ - **Styled HTML tags** - Pre-styled versions of all HTML elements
45
+ - **Components** - DataTable, NavBar, and more
46
+ - **Themes** - Light/dark themes via CSS variables
47
+ - **Type hints** - Full type annotations
48
+ - **Air integration** - Works seamlessly with Air framework
49
+
50
+ ## Plugins
51
+
52
+ ### Markdown
53
+
54
+ ```bash
55
+ pip install "eidosui[markdown]"
56
+ ```
57
+
58
+ ```python
59
+ from eidos.plugins.markdown import Markdown, MarkdownCSS
60
+
61
+ Head(
62
+ *EidosHeaders(),
63
+ MarkdownCSS() # Add markdown styles
64
+ )
65
+
66
+ Body(
67
+ Markdown("# Hello\n\nSupports **GitHub Flavored Markdown**")
68
+ )
69
+ ```
70
+
71
+ ## Documentation
72
+
73
+ Full documentation: https://eidosui.readthedocs.io
74
+
75
+ ## License
76
+
77
+ MIT
@@ -0,0 +1,270 @@
1
+ """EidosUI - A modern, flexible Tailwind CSS-based UI library for Python web frameworks.
2
+
3
+ Quick start:
4
+ >>> from eidos import *
5
+ >>> DataTable.from_lists([["A", "B"], ["C", "D"]], headers=["Col1", "Col2"])
6
+
7
+ Or use explicit imports:
8
+ >>> from eidos import DataTable, Button, H1, Table
9
+ >>> from eidos import styles
10
+ """
11
+
12
+ # Import all styled HTML tags
13
+ # Import style namespaces
14
+ from . import styles
15
+
16
+ # Import components
17
+ from .components import DataTable, EidosHeaders, NavBar
18
+ from .styles import buttons, lists, tables, typography
19
+ from .tags import (
20
+ # Headings
21
+ H1,
22
+ H2,
23
+ H3,
24
+ H4,
25
+ H5,
26
+ H6,
27
+ # Pass-through HTML tags
28
+ A,
29
+ Abbr,
30
+ Address,
31
+ Area,
32
+ Article,
33
+ Aside,
34
+ Audio,
35
+ B,
36
+ Base,
37
+ Bdi,
38
+ Bdo,
39
+ Blockquote,
40
+ # Body
41
+ Body,
42
+ Br,
43
+ # Buttons
44
+ Button,
45
+ Canvas,
46
+ Caption,
47
+ Cite,
48
+ Code,
49
+ Col,
50
+ Colgroup,
51
+ Data,
52
+ Datalist,
53
+ Dd,
54
+ Del,
55
+ Details,
56
+ Dfn,
57
+ Dialog,
58
+ Div,
59
+ Dl,
60
+ Dt,
61
+ Em,
62
+ Embed,
63
+ Fieldset,
64
+ Figcaption,
65
+ Figure,
66
+ Footer,
67
+ Form,
68
+ Head,
69
+ Header,
70
+ Hgroup,
71
+ Hr,
72
+ Html,
73
+ I,
74
+ Iframe,
75
+ Img,
76
+ Input,
77
+ Ins,
78
+ Kbd,
79
+ Label,
80
+ Legend,
81
+ Li,
82
+ Link,
83
+ Main,
84
+ Map,
85
+ Mark,
86
+ Menu,
87
+ Meta,
88
+ Meter,
89
+ Nav,
90
+ Noscript,
91
+ Object,
92
+ Ol,
93
+ Optgroup,
94
+ Option,
95
+ Output,
96
+ P,
97
+ Param,
98
+ Picture,
99
+ Pre,
100
+ Progress,
101
+ Q,
102
+ Rp,
103
+ Rt,
104
+ Ruby,
105
+ S,
106
+ Samp,
107
+ Script,
108
+ Search,
109
+ Section,
110
+ Select,
111
+ Small,
112
+ Source,
113
+ Span,
114
+ # Semantic typography
115
+ Strong,
116
+ Style,
117
+ Sub,
118
+ Summary,
119
+ Sup,
120
+ # Table elements
121
+ Table,
122
+ Tbody,
123
+ Td,
124
+ Template,
125
+ Textarea,
126
+ Tfoot,
127
+ Th,
128
+ Thead,
129
+ Time,
130
+ Title,
131
+ Tr,
132
+ Track,
133
+ U,
134
+ Ul,
135
+ Var,
136
+ Video,
137
+ Wbr,
138
+ )
139
+
140
+ # Version info
141
+ __version__ = "0.4.0"
142
+
143
+ # Define what's available with "from eidos import *"
144
+ __all__ = [
145
+ # Version
146
+ "__version__",
147
+ # Style namespaces
148
+ "styles",
149
+ "buttons",
150
+ "typography",
151
+ "lists",
152
+ "tables",
153
+ # Components
154
+ "DataTable",
155
+ "NavBar",
156
+ "EidosHeaders",
157
+ # HTML Tags
158
+ "H1",
159
+ "H2",
160
+ "H3",
161
+ "H4",
162
+ "H5",
163
+ "H6",
164
+ "Body",
165
+ "Button",
166
+ "Strong",
167
+ "I",
168
+ "Small",
169
+ "Del",
170
+ "Abbr",
171
+ "Var",
172
+ "Mark",
173
+ "Time",
174
+ "Code",
175
+ "Pre",
176
+ "Kbd",
177
+ "Samp",
178
+ "Blockquote",
179
+ "Cite",
180
+ "Address",
181
+ "Hr",
182
+ "Details",
183
+ "Summary",
184
+ "Dl",
185
+ "Dt",
186
+ "Dd",
187
+ "Figure",
188
+ "Figcaption",
189
+ "Table",
190
+ "Thead",
191
+ "Tbody",
192
+ "Tfoot",
193
+ "Tr",
194
+ "Th",
195
+ "Td",
196
+ "A",
197
+ "Area",
198
+ "Article",
199
+ "Aside",
200
+ "Audio",
201
+ "B",
202
+ "Base",
203
+ "Bdi",
204
+ "Bdo",
205
+ "Br",
206
+ "Canvas",
207
+ "Caption",
208
+ "Col",
209
+ "Colgroup",
210
+ "Data",
211
+ "Datalist",
212
+ "Dfn",
213
+ "Dialog",
214
+ "Div",
215
+ "Em",
216
+ "Embed",
217
+ "Fieldset",
218
+ "Footer",
219
+ "Form",
220
+ "Head",
221
+ "Header",
222
+ "Hgroup",
223
+ "Html",
224
+ "Iframe",
225
+ "Img",
226
+ "Input",
227
+ "Ins",
228
+ "Label",
229
+ "Legend",
230
+ "Li",
231
+ "Link",
232
+ "Main",
233
+ "Map",
234
+ "Menu",
235
+ "Meta",
236
+ "Meter",
237
+ "Nav",
238
+ "Noscript",
239
+ "Object",
240
+ "Ol",
241
+ "Optgroup",
242
+ "Option",
243
+ "Output",
244
+ "P",
245
+ "Param",
246
+ "Picture",
247
+ "Progress",
248
+ "Q",
249
+ "Rp",
250
+ "Rt",
251
+ "Ruby",
252
+ "S",
253
+ "Script",
254
+ "Search",
255
+ "Section",
256
+ "Select",
257
+ "Source",
258
+ "Span",
259
+ "Style",
260
+ "Sub",
261
+ "Sup",
262
+ "Template",
263
+ "Textarea",
264
+ "Title",
265
+ "Track",
266
+ "U",
267
+ "Ul",
268
+ "Video",
269
+ "Wbr",
270
+ ]
@@ -0,0 +1,11 @@
1
+ """EidosUI Components Package
2
+
3
+ Higher-level components built on top of the base tags.
4
+ """
5
+
6
+ from .headers import EidosHeaders
7
+ from .navigation import NavBar
8
+ from .table import DataTable
9
+ from .tabs import TabContainer, TabList, TabPanel, Tabs
10
+
11
+ __all__ = ["DataTable", "NavBar", "EidosHeaders", "TabContainer", "TabList", "TabPanel", "Tabs"]
@@ -1,16 +1,18 @@
1
- from air import Meta, Script, Link
2
- from ..tags import Body
3
1
  from typing import Literal
4
2
 
3
+ from air import Link, Meta, Script
4
+
5
+
5
6
  def get_css_urls():
6
7
  """Return list of CSS URLs for EidosUI."""
7
8
  return [
8
9
  "/eidos/css/styles.css",
9
- "/eidos/css/themes/eidos-variables.css",
10
+ "/eidos/css/themes/eidos-variables.css",
10
11
  "/eidos/css/themes/light.css",
11
- "/eidos/css/themes/dark.css"
12
+ "/eidos/css/themes/dark.css",
12
13
  ]
13
14
 
15
+
14
16
  def EidosHeaders(
15
17
  include_tailwind: bool = True,
16
18
  include_lucide: bool = True,
@@ -18,7 +20,7 @@ def EidosHeaders(
18
20
  theme: Literal["light", "dark"] = "light",
19
21
  ):
20
22
  """Complete EidosUI headers with EidosUI JavaScript support.
21
-
23
+
22
24
  Args:
23
25
  include_tailwind: Include Tailwind CSS CDN
24
26
  include_lucide: Include Lucide Icons CDN
@@ -29,28 +31,28 @@ def EidosHeaders(
29
31
  Meta(charset="UTF-8"),
30
32
  Meta(name="viewport", content="width=device-width, initial-scale=1.0"),
31
33
  ]
32
-
34
+
33
35
  # Core libraries
34
36
  if include_tailwind:
35
37
  headers.append(Script(src="https://cdn.tailwindcss.com"))
36
-
38
+
37
39
  if include_lucide:
38
40
  headers.append(Script(src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"))
39
-
41
+
40
42
  # EidosUI CSS
41
43
  for css_url in get_css_urls():
42
44
  headers.append(Link(rel="stylesheet", href=css_url))
43
-
45
+
44
46
  # EidosUI JavaScript
45
47
  if include_eidos_js:
46
48
  headers.append(Script(src="/eidos/js/eidos.js", defer=True))
47
-
49
+
48
50
  # Initialization script
49
51
  init_script = f"""
50
52
  // Set theme
51
53
  document.documentElement.setAttribute('data-theme', '{theme}');
52
54
  """
53
-
55
+
54
56
  if include_lucide:
55
57
  init_script += """
56
58
  // Initialize Lucide icons
@@ -62,7 +64,7 @@ def EidosHeaders(
62
64
  if (window.lucide) lucide.createIcons();
63
65
  }
64
66
  """
65
-
67
+
66
68
  headers.append(Script(init_script))
67
-
68
- return headers
69
+
70
+ return headers
@@ -0,0 +1,87 @@
1
+ from typing import Any, Final
2
+ from uuid import uuid4
3
+
4
+ from air import A, Div, I, Tag
5
+
6
+ from ..tags import *
7
+ from ..utils import stringify
8
+
9
+
10
+ class ScrollspyT:
11
+ underline: Final[str] = "navbar-underline"
12
+ bold: Final[str] = "navbar-bold"
13
+
14
+
15
+ def NavBar(
16
+ *c: Any,
17
+ lcontents: Tag | None = None,
18
+ right_cls: str = "items-center space-x-4",
19
+ mobile_cls: str = "",
20
+ sticky: bool = False,
21
+ scrollspy: bool = False,
22
+ cls: str = "p-4",
23
+ scrollspy_cls: str = ScrollspyT.underline,
24
+ menu_id: str | None = None,
25
+ ) -> Tag:
26
+ """Pure Tailwind responsive navigation bar with optional scrollspy.
27
+
28
+ Mobile menu uses best practice dropdown with:
29
+ - Centered text links
30
+ - Large touch targets
31
+ - Auto-close on selection
32
+ - Smooth animations
33
+ """
34
+ if lcontents is None:
35
+ lcontents = Div()
36
+
37
+ if menu_id is None:
38
+ menu_id = f"menu-{uuid4().hex[:8]}"
39
+
40
+ sticky_cls = "sticky top-0 eidos-navbar-sticky z-50" if sticky else ""
41
+
42
+ # Mobile toggle button with hamburger/close icon
43
+ mobile_icon = A(
44
+ I(data_lucide="menu", class_="w-6 h-6", data_menu_icon="open"),
45
+ I(data_lucide="x", class_="w-6 h-6 hidden", data_menu_icon="close"),
46
+ class_="md:hidden cursor-pointer p-2 eidos-navbar-toggle rounded-lg transition-colors",
47
+ data_toggle=f"#{menu_id}",
48
+ role="button",
49
+ aria_label="Toggle navigation",
50
+ aria_expanded="false",
51
+ )
52
+
53
+ # Desktop navigation
54
+ desktop_nav = Div(
55
+ *c,
56
+ class_=stringify(right_cls, "hidden md:flex"),
57
+ data_scrollspy="true" if scrollspy else None,
58
+ )
59
+
60
+ # Mobile navigation
61
+ mobile_nav = Div(
62
+ *c,
63
+ class_=stringify(
64
+ mobile_cls,
65
+ "hidden md:hidden absolute top-full left-0 right-0 eidos-navbar-mobile shadow-lg border-t",
66
+ "flex flex-col eidos-navbar-mobile-divider" if not mobile_cls else "",
67
+ scrollspy_cls,
68
+ ),
69
+ id=menu_id,
70
+ data_scrollspy="true" if scrollspy else None,
71
+ data_mobile_menu="true",
72
+ )
73
+
74
+ return Div(
75
+ # Main navbar container with relative positioning for mobile dropdown
76
+ Div(
77
+ Div(
78
+ lcontents,
79
+ mobile_icon,
80
+ desktop_nav,
81
+ class_="flex items-center justify-between",
82
+ ),
83
+ mobile_nav,
84
+ class_=stringify("eidos-navbar relative", cls, scrollspy_cls),
85
+ ),
86
+ class_=sticky_cls,
87
+ )
@@ -0,0 +1,84 @@
1
+ from typing import Any
2
+
3
+ from ..tags import Table as BaseTable
4
+ from ..tags import Tbody, Td, Th, Thead, Tr
5
+
6
+
7
+ class DataTable:
8
+ """DataTable component that provides convenient methods for creating tables from data."""
9
+
10
+ @classmethod
11
+ def from_lists(
12
+ cls,
13
+ data: list[list],
14
+ headers: list[str] | None = None,
15
+ class_: str | list[str] | None = None,
16
+ **kwargs: Any,
17
+ ) -> Any:
18
+ """Create table from list of lists.
19
+
20
+ Args:
21
+ data: List of lists where each inner list is a row
22
+ headers: Optional list of header strings
23
+ class_: Optional CSS classes to add to the table
24
+ **kwargs: Additional attributes to pass to the table element
25
+
26
+ Returns:
27
+ A rendered table element
28
+
29
+ Example:
30
+ Table.from_lists([["A", "B"], ["C", "D"]], headers=["Col1", "Col2"])
31
+ """
32
+ content = []
33
+
34
+ if headers:
35
+ thead = Thead(Tr(*[Th(header) for header in headers]))
36
+ content.append(thead)
37
+
38
+ tbody_rows = []
39
+ for row_data in data:
40
+ tbody_rows.append(Tr(*[Td(cell) for cell in row_data]))
41
+ tbody = Tbody(*tbody_rows)
42
+ content.append(tbody)
43
+
44
+ return BaseTable(*content, class_=class_, **kwargs)
45
+
46
+ @classmethod
47
+ def from_dicts(
48
+ cls,
49
+ data: list[dict],
50
+ headers: list[str] | None = None,
51
+ class_: str | list[str] | None = None,
52
+ **kwargs: Any,
53
+ ) -> Any:
54
+ """Create table from list of dictionaries.
55
+
56
+ Args:
57
+ data: List of dictionaries where each dict is a row
58
+ headers: Optional list of header strings. If not provided, uses keys from first dict
59
+ class_: Optional CSS classes to add to the table
60
+ **kwargs: Additional attributes to pass to the table element
61
+
62
+ Returns:
63
+ A rendered table element
64
+
65
+ Example:
66
+ Table.from_dicts([{"name": "John", "age": 25}], headers=["name", "age"])
67
+ """
68
+ if headers is None and data:
69
+ headers = list(data[0].keys())
70
+
71
+ content = []
72
+
73
+ if headers:
74
+ thead = Thead(Tr(*[Th(header) for header in headers]))
75
+ content.append(thead)
76
+
77
+ tbody_rows = []
78
+ if data and headers:
79
+ for row in data:
80
+ tbody_rows.append(Tr(*[Td(row.get(key, "")) for key in headers]))
81
+ tbody = Tbody(*tbody_rows)
82
+ content.append(tbody)
83
+
84
+ return BaseTable(*content, class_=class_, **kwargs)