claide-web 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.
- claide_web-0.1.0/.gitignore +8 -0
- claide_web-0.1.0/LICENSE +21 -0
- claide_web-0.1.0/PKG-INFO +230 -0
- claide_web-0.1.0/README.md +206 -0
- claide_web-0.1.0/claide/__init__.py +26 -0
- claide_web-0.1.0/claide/__main__.py +3 -0
- claide_web-0.1.0/claide/app.py +947 -0
- claide_web-0.1.0/claide/cli.py +173 -0
- claide_web-0.1.0/claide/components.py +163 -0
- claide_web-0.1.0/claide/html.py +212 -0
- claide_web-0.1.0/claide/page.py +167 -0
- claide_web-0.1.0/claide/py.typed +0 -0
- claide_web-0.1.0/claide/server.py +82 -0
- claide_web-0.1.0/claide/uitemplate.py +505 -0
- claide_web-0.1.0/examples/full_site.py +115 -0
- claide_web-0.1.0/examples/static/script.js +2 -0
- claide_web-0.1.0/examples/static/style.css +2 -0
- claide_web-0.1.0/pyproject.toml +48 -0
- claide_web-0.1.0/tests/test_app.py +150 -0
- claide_web-0.1.0/tests/test_html.py +30 -0
- claide_web-0.1.0/tests/test_uitemplate.py +100 -0
claide_web-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kurodaki
|
|
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,230 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: claide-web
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Build complete websites in Python: pages, routes, forms, APIs, sessions and Uiverse.io components.
|
|
5
|
+
Project-URL: Homepage, https://github.com/kurodaki-dev/Claide
|
|
6
|
+
Project-URL: Issues, https://github.com/kurodaki-dev/Claide/issues
|
|
7
|
+
Author: kurodaki-dev
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: css,framework,html,uiverse,web,website,wsgi
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Natural Language :: English
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
18
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# Claide ✦
|
|
26
|
+
|
|
27
|
+
**Build complete websites in Python, from A to Z**: HTML pages, dynamic routes,
|
|
28
|
+
forms, file uploads, JSON APIs, sessions, your own `style.css` / `script.js`, and
|
|
29
|
+
the thousands of components from [cssbuttons.io](https://cssbuttons.io) / [Uiverse.io](https://uiverse.io)
|
|
30
|
+
in a single line. Zero dependencies.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install claide-web
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The package is published on PyPI as `claide-web`, but you import it as `claide`.
|
|
37
|
+
|
|
38
|
+
## Quick start
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
claide new mysite
|
|
42
|
+
cd mysite
|
|
43
|
+
python app.py # -> http://127.0.0.1:8000 (auto-reload)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from claide import App, Page, tags as t
|
|
48
|
+
|
|
49
|
+
app = App(__name__)
|
|
50
|
+
|
|
51
|
+
@app.route("/")
|
|
52
|
+
def home():
|
|
53
|
+
return Page("Home", t.h1("Hello!"), t.p("My first Claide website."))
|
|
54
|
+
|
|
55
|
+
app.run()
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Uiverse / cssbuttons.io components
|
|
59
|
+
|
|
60
|
+
Copy the URL of an element on uiverse.io (cssbuttons.io redirects to uiverse.io/buttons):
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from claide import UITemplate # or: from claide import uitemplate
|
|
64
|
+
|
|
65
|
+
button = UITemplate("https://uiverse.io/0x-Sarthak/hungry-penguin-30")
|
|
66
|
+
# or UITemplate("0x-Sarthak/hungry-penguin-30")
|
|
67
|
+
|
|
68
|
+
page.add(
|
|
69
|
+
button
|
|
70
|
+
.text("Contact Us", "Write me") # change the text
|
|
71
|
+
.attrs(id="send", onclick="go()") # add attributes
|
|
72
|
+
.link("/contact") # turn it into a link
|
|
73
|
+
)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- The component's CSS is **injected automatically** into the `<head>` (only once).
|
|
77
|
+
- Tailwind-based components load Tailwind by themselves.
|
|
78
|
+
- `.scoped()` / `.prefix("x-")` rename the CSS classes to avoid conflicts
|
|
79
|
+
(e.g. two components that both use `.button`).
|
|
80
|
+
- `.add_css("...")` lets you override colors.
|
|
81
|
+
- `UITemplate(html="...", css="...")` or `UITemplate.from_file("button.html")` for your own code.
|
|
82
|
+
- `UITemplate.builtin("glow", "Send")`: bundled components that work offline
|
|
83
|
+
(`gradient`, `glow`, `slide`, `pushable`, `spinner`, `toggle`, `card`).
|
|
84
|
+
- Components are downloaded from the official `uiverse-io/galaxy` repository
|
|
85
|
+
(MIT license) and cached in `~/.cache/claide`.
|
|
86
|
+
|
|
87
|
+
From the command line: `claide ui author/name --preview` or `--save button.html`.
|
|
88
|
+
|
|
89
|
+
## Linking your CSS / JS files
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
mysite/
|
|
93
|
+
├── app.py
|
|
94
|
+
└── static/
|
|
95
|
+
├── style.css
|
|
96
|
+
├── script.js
|
|
97
|
+
└── logo.png
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
app.css(app.static("style.css")) # on every page
|
|
102
|
+
app.js(app.static("script.js"))
|
|
103
|
+
|
|
104
|
+
page = Page("Contact")
|
|
105
|
+
page.css("/static/contact.css") # on a single page
|
|
106
|
+
page.js("https://cdn.jsdelivr.net/npm/chart.js")
|
|
107
|
+
page.style("h1 { color: red }") # inline CSS
|
|
108
|
+
page.script("console.log('hi')") # inline JS
|
|
109
|
+
page.style_file("theme.css") # inline the content of a file
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Building HTML in Python
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from claide import tags as t, raw
|
|
116
|
+
|
|
117
|
+
t.div(
|
|
118
|
+
t.h1("Title", class_="big"), # class_ -> class
|
|
119
|
+
t.a("Link", href="/x", data_id=3), # data_id -> data-id
|
|
120
|
+
t.input(type="checkbox", checked=True),
|
|
121
|
+
t.ul([t.li(x) for x in ["a", "b"]]),
|
|
122
|
+
raw("<b>raw HTML</b>"),
|
|
123
|
+
style={"padding": "1rem"},
|
|
124
|
+
)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Text is escaped automatically (XSS protection) except with `raw()`.
|
|
128
|
+
|
|
129
|
+
### Layout components
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from claide.components import Navbar, Hero, Section, Grid, Card, Footer, Form, Field, Button, Alert
|
|
133
|
+
|
|
134
|
+
Page("Home",
|
|
135
|
+
Navbar("My site", [("Home", "/"), ("Contact", "/contact")]),
|
|
136
|
+
Hero("Welcome", "Subtitle", Button("Get started", "/start")),
|
|
137
|
+
Section(Grid(Card("A", "text"), Card("B", "text", image="/static/b.jpg")), title="Services"),
|
|
138
|
+
Footer("© 2026"),
|
|
139
|
+
)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Dynamic server
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
from claide import App, Page, Router, redirect, abort, send_file
|
|
146
|
+
|
|
147
|
+
app = App(__name__, secret_key="change-me")
|
|
148
|
+
|
|
149
|
+
@app.get("/user/<int:uid>") # converters: str, int, float, slug, path
|
|
150
|
+
def user(uid, request):
|
|
151
|
+
page = request.args.get("page", "1") # ?page=2
|
|
152
|
+
return Page(f"User {uid}", ...)
|
|
153
|
+
|
|
154
|
+
@app.route("/contact", methods=["GET", "POST"])
|
|
155
|
+
def contact(request):
|
|
156
|
+
if request.method == "POST":
|
|
157
|
+
name = request.form["name"]
|
|
158
|
+
request.files["cv"].save("uploads/") # file upload
|
|
159
|
+
request.session["name"] = name # session (signed cookie)
|
|
160
|
+
return redirect("/thanks")
|
|
161
|
+
return Page(...)
|
|
162
|
+
|
|
163
|
+
@app.post("/api/items")
|
|
164
|
+
def create(request):
|
|
165
|
+
data = request.json
|
|
166
|
+
return {"ok": True, "item": data}, 201 # dict/list -> JSON
|
|
167
|
+
|
|
168
|
+
@app.errorhandler(404)
|
|
169
|
+
def not_found():
|
|
170
|
+
return Page("Oops", t.h1("Page not found"))
|
|
171
|
+
|
|
172
|
+
@app.layout # applied to every Page
|
|
173
|
+
def base(page):
|
|
174
|
+
page.body.children.insert(0, navbar)
|
|
175
|
+
|
|
176
|
+
@app.before_request
|
|
177
|
+
def auth(request):
|
|
178
|
+
if request.path.startswith("/admin") and not request.session.get("admin"):
|
|
179
|
+
return redirect("/login")
|
|
180
|
+
|
|
181
|
+
api = Router("/api/v2")
|
|
182
|
+
@api.get("/ping")
|
|
183
|
+
def ping(): return {"pong": True}
|
|
184
|
+
app.include(api)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
A route can return: a `Page`, an element, a `UITemplate`, `str`, `dict`/`list` (JSON),
|
|
188
|
+
a `Response`, a generator (streaming), or a `(body, status, headers)` tuple.
|
|
189
|
+
|
|
190
|
+
### Production
|
|
191
|
+
|
|
192
|
+
`App` is a standard **WSGI** application:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
pip install gunicorn
|
|
196
|
+
gunicorn app:app
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Static export (optional)
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
claide build app:app --out dist # or app.build("dist")
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Testing
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
client = app.test_client()
|
|
209
|
+
assert client.get("/").status == 200
|
|
210
|
+
assert client.post("/api/items", json_data={"a": 1}).json["ok"]
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Automatic PyPI publishing
|
|
214
|
+
|
|
215
|
+
`.github/workflows/ci.yml` runs the tests, builds the package, then publishes it
|
|
216
|
+
to PyPI on every push to `main` (and on `v*` tags). If the version already exists,
|
|
217
|
+
nothing is uploaded: **bump `__version__` in `claide/__init__.py` to release.**
|
|
218
|
+
|
|
219
|
+
One-time setup:
|
|
220
|
+
|
|
221
|
+
1. On [pypi.org](https://pypi.org/manage/account/token/), create an API token.
|
|
222
|
+
2. On GitHub: *Settings → Secrets and variables → Actions → New repository secret*,
|
|
223
|
+
name **`PYPI_API_TOKEN`**, value = the token (`pypi-...`).
|
|
224
|
+
3. The job uses the `pypi` GitHub environment (created automatically; you can add
|
|
225
|
+
a manual approval in *Settings → Environments*).
|
|
226
|
+
|
|
227
|
+
## License
|
|
228
|
+
|
|
229
|
+
MIT. Uiverse.io components are MIT licensed; please credit their authors
|
|
230
|
+
(Claide automatically adds a credit comment in the CSS).
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Claide ✦
|
|
2
|
+
|
|
3
|
+
**Build complete websites in Python, from A to Z**: HTML pages, dynamic routes,
|
|
4
|
+
forms, file uploads, JSON APIs, sessions, your own `style.css` / `script.js`, and
|
|
5
|
+
the thousands of components from [cssbuttons.io](https://cssbuttons.io) / [Uiverse.io](https://uiverse.io)
|
|
6
|
+
in a single line. Zero dependencies.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install claide-web
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The package is published on PyPI as `claide-web`, but you import it as `claide`.
|
|
13
|
+
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
claide new mysite
|
|
18
|
+
cd mysite
|
|
19
|
+
python app.py # -> http://127.0.0.1:8000 (auto-reload)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from claide import App, Page, tags as t
|
|
24
|
+
|
|
25
|
+
app = App(__name__)
|
|
26
|
+
|
|
27
|
+
@app.route("/")
|
|
28
|
+
def home():
|
|
29
|
+
return Page("Home", t.h1("Hello!"), t.p("My first Claide website."))
|
|
30
|
+
|
|
31
|
+
app.run()
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Uiverse / cssbuttons.io components
|
|
35
|
+
|
|
36
|
+
Copy the URL of an element on uiverse.io (cssbuttons.io redirects to uiverse.io/buttons):
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from claide import UITemplate # or: from claide import uitemplate
|
|
40
|
+
|
|
41
|
+
button = UITemplate("https://uiverse.io/0x-Sarthak/hungry-penguin-30")
|
|
42
|
+
# or UITemplate("0x-Sarthak/hungry-penguin-30")
|
|
43
|
+
|
|
44
|
+
page.add(
|
|
45
|
+
button
|
|
46
|
+
.text("Contact Us", "Write me") # change the text
|
|
47
|
+
.attrs(id="send", onclick="go()") # add attributes
|
|
48
|
+
.link("/contact") # turn it into a link
|
|
49
|
+
)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- The component's CSS is **injected automatically** into the `<head>` (only once).
|
|
53
|
+
- Tailwind-based components load Tailwind by themselves.
|
|
54
|
+
- `.scoped()` / `.prefix("x-")` rename the CSS classes to avoid conflicts
|
|
55
|
+
(e.g. two components that both use `.button`).
|
|
56
|
+
- `.add_css("...")` lets you override colors.
|
|
57
|
+
- `UITemplate(html="...", css="...")` or `UITemplate.from_file("button.html")` for your own code.
|
|
58
|
+
- `UITemplate.builtin("glow", "Send")`: bundled components that work offline
|
|
59
|
+
(`gradient`, `glow`, `slide`, `pushable`, `spinner`, `toggle`, `card`).
|
|
60
|
+
- Components are downloaded from the official `uiverse-io/galaxy` repository
|
|
61
|
+
(MIT license) and cached in `~/.cache/claide`.
|
|
62
|
+
|
|
63
|
+
From the command line: `claide ui author/name --preview` or `--save button.html`.
|
|
64
|
+
|
|
65
|
+
## Linking your CSS / JS files
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
mysite/
|
|
69
|
+
├── app.py
|
|
70
|
+
└── static/
|
|
71
|
+
├── style.css
|
|
72
|
+
├── script.js
|
|
73
|
+
└── logo.png
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
app.css(app.static("style.css")) # on every page
|
|
78
|
+
app.js(app.static("script.js"))
|
|
79
|
+
|
|
80
|
+
page = Page("Contact")
|
|
81
|
+
page.css("/static/contact.css") # on a single page
|
|
82
|
+
page.js("https://cdn.jsdelivr.net/npm/chart.js")
|
|
83
|
+
page.style("h1 { color: red }") # inline CSS
|
|
84
|
+
page.script("console.log('hi')") # inline JS
|
|
85
|
+
page.style_file("theme.css") # inline the content of a file
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Building HTML in Python
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from claide import tags as t, raw
|
|
92
|
+
|
|
93
|
+
t.div(
|
|
94
|
+
t.h1("Title", class_="big"), # class_ -> class
|
|
95
|
+
t.a("Link", href="/x", data_id=3), # data_id -> data-id
|
|
96
|
+
t.input(type="checkbox", checked=True),
|
|
97
|
+
t.ul([t.li(x) for x in ["a", "b"]]),
|
|
98
|
+
raw("<b>raw HTML</b>"),
|
|
99
|
+
style={"padding": "1rem"},
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Text is escaped automatically (XSS protection) except with `raw()`.
|
|
104
|
+
|
|
105
|
+
### Layout components
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from claide.components import Navbar, Hero, Section, Grid, Card, Footer, Form, Field, Button, Alert
|
|
109
|
+
|
|
110
|
+
Page("Home",
|
|
111
|
+
Navbar("My site", [("Home", "/"), ("Contact", "/contact")]),
|
|
112
|
+
Hero("Welcome", "Subtitle", Button("Get started", "/start")),
|
|
113
|
+
Section(Grid(Card("A", "text"), Card("B", "text", image="/static/b.jpg")), title="Services"),
|
|
114
|
+
Footer("© 2026"),
|
|
115
|
+
)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Dynamic server
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from claide import App, Page, Router, redirect, abort, send_file
|
|
122
|
+
|
|
123
|
+
app = App(__name__, secret_key="change-me")
|
|
124
|
+
|
|
125
|
+
@app.get("/user/<int:uid>") # converters: str, int, float, slug, path
|
|
126
|
+
def user(uid, request):
|
|
127
|
+
page = request.args.get("page", "1") # ?page=2
|
|
128
|
+
return Page(f"User {uid}", ...)
|
|
129
|
+
|
|
130
|
+
@app.route("/contact", methods=["GET", "POST"])
|
|
131
|
+
def contact(request):
|
|
132
|
+
if request.method == "POST":
|
|
133
|
+
name = request.form["name"]
|
|
134
|
+
request.files["cv"].save("uploads/") # file upload
|
|
135
|
+
request.session["name"] = name # session (signed cookie)
|
|
136
|
+
return redirect("/thanks")
|
|
137
|
+
return Page(...)
|
|
138
|
+
|
|
139
|
+
@app.post("/api/items")
|
|
140
|
+
def create(request):
|
|
141
|
+
data = request.json
|
|
142
|
+
return {"ok": True, "item": data}, 201 # dict/list -> JSON
|
|
143
|
+
|
|
144
|
+
@app.errorhandler(404)
|
|
145
|
+
def not_found():
|
|
146
|
+
return Page("Oops", t.h1("Page not found"))
|
|
147
|
+
|
|
148
|
+
@app.layout # applied to every Page
|
|
149
|
+
def base(page):
|
|
150
|
+
page.body.children.insert(0, navbar)
|
|
151
|
+
|
|
152
|
+
@app.before_request
|
|
153
|
+
def auth(request):
|
|
154
|
+
if request.path.startswith("/admin") and not request.session.get("admin"):
|
|
155
|
+
return redirect("/login")
|
|
156
|
+
|
|
157
|
+
api = Router("/api/v2")
|
|
158
|
+
@api.get("/ping")
|
|
159
|
+
def ping(): return {"pong": True}
|
|
160
|
+
app.include(api)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
A route can return: a `Page`, an element, a `UITemplate`, `str`, `dict`/`list` (JSON),
|
|
164
|
+
a `Response`, a generator (streaming), or a `(body, status, headers)` tuple.
|
|
165
|
+
|
|
166
|
+
### Production
|
|
167
|
+
|
|
168
|
+
`App` is a standard **WSGI** application:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
pip install gunicorn
|
|
172
|
+
gunicorn app:app
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Static export (optional)
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
claide build app:app --out dist # or app.build("dist")
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Testing
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
client = app.test_client()
|
|
185
|
+
assert client.get("/").status == 200
|
|
186
|
+
assert client.post("/api/items", json_data={"a": 1}).json["ok"]
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Automatic PyPI publishing
|
|
190
|
+
|
|
191
|
+
`.github/workflows/ci.yml` runs the tests, builds the package, then publishes it
|
|
192
|
+
to PyPI on every push to `main` (and on `v*` tags). If the version already exists,
|
|
193
|
+
nothing is uploaded: **bump `__version__` in `claide/__init__.py` to release.**
|
|
194
|
+
|
|
195
|
+
One-time setup:
|
|
196
|
+
|
|
197
|
+
1. On [pypi.org](https://pypi.org/manage/account/token/), create an API token.
|
|
198
|
+
2. On GitHub: *Settings → Secrets and variables → Actions → New repository secret*,
|
|
199
|
+
name **`PYPI_API_TOKEN`**, value = the token (`pypi-...`).
|
|
200
|
+
3. The job uses the `pypi` GitHub environment (created automatically; you can add
|
|
201
|
+
a manual approval in *Settings → Environments*).
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
MIT. Uiverse.io components are MIT licensed; please credit their authors
|
|
206
|
+
(Claide automatically adds a credit comment in the CSS).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Claide — build complete websites in Python, from A to Z.
|
|
2
|
+
|
|
3
|
+
from claide import App, Page, UITemplate, tags as t
|
|
4
|
+
|
|
5
|
+
See https://github.com/kurodaki-dev/Claide
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .app import (
|
|
9
|
+
App, HTTPError, Request, Response, Router, UploadedFile, abort, redirect, send_file,
|
|
10
|
+
)
|
|
11
|
+
from .html import Element, Fragment, Raw, escape, raw, tags
|
|
12
|
+
from .page import Page
|
|
13
|
+
from .uitemplate import CATEGORIES, UITemplate, UITemplateError, builtin_templates
|
|
14
|
+
|
|
15
|
+
# Also allows "from claide import uitemplate" then uitemplate.UITemplate(...)
|
|
16
|
+
from . import components, uitemplate # noqa: E402
|
|
17
|
+
|
|
18
|
+
__version__ = "0.1.0"
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"App", "Router", "Request", "Response", "UploadedFile", "HTTPError",
|
|
22
|
+
"abort", "redirect", "send_file",
|
|
23
|
+
"Element", "Fragment", "Raw", "escape", "raw", "tags",
|
|
24
|
+
"Page", "UITemplate", "UITemplateError", "CATEGORIES", "builtin_templates",
|
|
25
|
+
"components", "uitemplate", "__version__",
|
|
26
|
+
]
|