WebForge 1.0.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.
- webforge-1.0.0/PKG-INFO +142 -0
- webforge-1.0.0/README.md +122 -0
- webforge-1.0.0/WebForge/__init__.py +93 -0
- webforge-1.0.0/WebForge/core.py +222 -0
- webforge-1.0.0/WebForge.egg-info/PKG-INFO +142 -0
- webforge-1.0.0/WebForge.egg-info/SOURCES.txt +12 -0
- webforge-1.0.0/WebForge.egg-info/dependency_links.txt +1 -0
- webforge-1.0.0/WebForge.egg-info/entry_points.txt +2 -0
- webforge-1.0.0/WebForge.egg-info/requires.txt +2 -0
- webforge-1.0.0/WebForge.egg-info/top_level.txt +2 -0
- webforge-1.0.0/WebForge_cli/cli.py +248 -0
- webforge-1.0.0/pyproject.toml +26 -0
- webforge-1.0.0/setup.cfg +4 -0
- webforge-1.0.0/setup.py +41 -0
webforge-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: WebForge
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: π₯ Create web applications easily β a Flask-powered micro-framework
|
|
5
|
+
Home-page: https://github.com/yourname/webforge
|
|
6
|
+
Author: Your Name
|
|
7
|
+
Author-email: you@example.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Homepage, https://github.com/yourname/webforge
|
|
10
|
+
Project-URL: Repository, https://github.com/yourname/webforge
|
|
11
|
+
Project-URL: Issues, https://github.com/yourname/webforge/issues
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: flask>=3.0.0
|
|
15
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
16
|
+
Dynamic: author
|
|
17
|
+
Dynamic: author-email
|
|
18
|
+
Dynamic: home-page
|
|
19
|
+
Dynamic: requires-python
|
|
20
|
+
|
|
21
|
+
# π₯ WebForge
|
|
22
|
+
|
|
23
|
+
**Create web applications easily** β a minimal Flask-powered framework with clean syntax.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install WebForge
|
|
27
|
+
webforge new mon-site
|
|
28
|
+
cd mon-site
|
|
29
|
+
python backend/server/forge.py
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Structure d'un projet WebForge
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
mon-site/
|
|
38
|
+
βββ backend/
|
|
39
|
+
β βββ requirements.txt
|
|
40
|
+
β βββ server/
|
|
41
|
+
β βββ forge.py β CΕur du serveur
|
|
42
|
+
βββ public/
|
|
43
|
+
βββ pages/
|
|
44
|
+
β βββ index.html β Templates HTML
|
|
45
|
+
βββ script/
|
|
46
|
+
β βββ app.js β JavaScript client
|
|
47
|
+
βββ service/
|
|
48
|
+
βββ style.css β Fichiers statiques
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Γcrire `forge.py`
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from WebForge import webforge, security, env as web
|
|
57
|
+
|
|
58
|
+
# Variables d'environnement
|
|
59
|
+
password = web.env("PASSWORD")
|
|
60
|
+
ip = "192.168.1.123"
|
|
61
|
+
|
|
62
|
+
# βββ Routes ββββββββββββββββββββββββββββββββββββββ
|
|
63
|
+
@web.app("/")
|
|
64
|
+
def index():
|
|
65
|
+
web.route("style", "/public/service/style.css")
|
|
66
|
+
return web.render("index.html")
|
|
67
|
+
|
|
68
|
+
@web.app("/login")
|
|
69
|
+
def login():
|
|
70
|
+
return web.render("login.html")
|
|
71
|
+
|
|
72
|
+
# βββ SΓ©curitΓ© ββββββββββββββββββββββββββββββββββββ
|
|
73
|
+
@web.security("/")
|
|
74
|
+
def secure_home():
|
|
75
|
+
if not web.password(password): # vΓ©rif mot de passe en session
|
|
76
|
+
return web.redirect("/login")
|
|
77
|
+
if not web.ip(ip): # vΓ©rif IP
|
|
78
|
+
return web.abort(403)
|
|
79
|
+
|
|
80
|
+
# βββ DΓ©marrage βββββββββββββββββββββββββββββββββββ
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
web.runapp(debug=True, port=5000, host="0.0.0.0")
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## API complète
|
|
88
|
+
|
|
89
|
+
### Routes
|
|
90
|
+
|
|
91
|
+
| Fonction | Description |
|
|
92
|
+
|----------|-------------|
|
|
93
|
+
| `@web.app("/path")` | DΓ©finit une route |
|
|
94
|
+
| `@web.security("/path")` | Middleware de sΓ©curitΓ© pour une route |
|
|
95
|
+
| `web.route("name", "/path/to/file")` | Route vers un fichier statique |
|
|
96
|
+
|
|
97
|
+
### RΓ©ponses
|
|
98
|
+
|
|
99
|
+
| Fonction | Description |
|
|
100
|
+
|----------|-------------|
|
|
101
|
+
| `web.render("page.html", **ctx)` | Rend un template HTML |
|
|
102
|
+
| `web.redirect("/url")` | Redirige vers une URL |
|
|
103
|
+
| `web.abort(403)` | Retourne une erreur HTTP |
|
|
104
|
+
|
|
105
|
+
### SΓ©curitΓ©
|
|
106
|
+
|
|
107
|
+
| Fonction | Description |
|
|
108
|
+
|----------|-------------|
|
|
109
|
+
| `web.password("secret")` | VΓ©rifie le mot de passe en session |
|
|
110
|
+
| `web.ip("192.168.1.1")` | VΓ©rifie l'IP du client (accepte aussi une liste) |
|
|
111
|
+
|
|
112
|
+
### Utilitaires
|
|
113
|
+
|
|
114
|
+
| Fonction | Description |
|
|
115
|
+
|----------|-------------|
|
|
116
|
+
| `web.env("VAR")` | Lit une variable d'environnement |
|
|
117
|
+
| `web.runapp(debug, port, host)` | DΓ©marre le serveur |
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## CLI
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
webforge new <nom> # CrΓ©e un nouveau projet
|
|
125
|
+
webforge run # Lance le projet courant
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Installation pour le dΓ©veloppement
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
git clone https://github.com/yourname/webforge
|
|
134
|
+
cd webforge
|
|
135
|
+
pip install -e .
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Licence
|
|
141
|
+
|
|
142
|
+
MIT
|
webforge-1.0.0/README.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# π₯ WebForge
|
|
2
|
+
|
|
3
|
+
**Create web applications easily** β a minimal Flask-powered framework with clean syntax.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install WebForge
|
|
7
|
+
webforge new mon-site
|
|
8
|
+
cd mon-site
|
|
9
|
+
python backend/server/forge.py
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Structure d'un projet WebForge
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
mon-site/
|
|
18
|
+
βββ backend/
|
|
19
|
+
β βββ requirements.txt
|
|
20
|
+
β βββ server/
|
|
21
|
+
β βββ forge.py β CΕur du serveur
|
|
22
|
+
βββ public/
|
|
23
|
+
βββ pages/
|
|
24
|
+
β βββ index.html β Templates HTML
|
|
25
|
+
βββ script/
|
|
26
|
+
β βββ app.js β JavaScript client
|
|
27
|
+
βββ service/
|
|
28
|
+
βββ style.css β Fichiers statiques
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Γcrire `forge.py`
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from WebForge import webforge, security, env as web
|
|
37
|
+
|
|
38
|
+
# Variables d'environnement
|
|
39
|
+
password = web.env("PASSWORD")
|
|
40
|
+
ip = "192.168.1.123"
|
|
41
|
+
|
|
42
|
+
# βββ Routes ββββββββββββββββββββββββββββββββββββββ
|
|
43
|
+
@web.app("/")
|
|
44
|
+
def index():
|
|
45
|
+
web.route("style", "/public/service/style.css")
|
|
46
|
+
return web.render("index.html")
|
|
47
|
+
|
|
48
|
+
@web.app("/login")
|
|
49
|
+
def login():
|
|
50
|
+
return web.render("login.html")
|
|
51
|
+
|
|
52
|
+
# βββ SΓ©curitΓ© ββββββββββββββββββββββββββββββββββββ
|
|
53
|
+
@web.security("/")
|
|
54
|
+
def secure_home():
|
|
55
|
+
if not web.password(password): # vΓ©rif mot de passe en session
|
|
56
|
+
return web.redirect("/login")
|
|
57
|
+
if not web.ip(ip): # vΓ©rif IP
|
|
58
|
+
return web.abort(403)
|
|
59
|
+
|
|
60
|
+
# βββ DΓ©marrage βββββββββββββββββββββββββββββββββββ
|
|
61
|
+
if __name__ == "__main__":
|
|
62
|
+
web.runapp(debug=True, port=5000, host="0.0.0.0")
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## API complète
|
|
68
|
+
|
|
69
|
+
### Routes
|
|
70
|
+
|
|
71
|
+
| Fonction | Description |
|
|
72
|
+
|----------|-------------|
|
|
73
|
+
| `@web.app("/path")` | DΓ©finit une route |
|
|
74
|
+
| `@web.security("/path")` | Middleware de sΓ©curitΓ© pour une route |
|
|
75
|
+
| `web.route("name", "/path/to/file")` | Route vers un fichier statique |
|
|
76
|
+
|
|
77
|
+
### RΓ©ponses
|
|
78
|
+
|
|
79
|
+
| Fonction | Description |
|
|
80
|
+
|----------|-------------|
|
|
81
|
+
| `web.render("page.html", **ctx)` | Rend un template HTML |
|
|
82
|
+
| `web.redirect("/url")` | Redirige vers une URL |
|
|
83
|
+
| `web.abort(403)` | Retourne une erreur HTTP |
|
|
84
|
+
|
|
85
|
+
### SΓ©curitΓ©
|
|
86
|
+
|
|
87
|
+
| Fonction | Description |
|
|
88
|
+
|----------|-------------|
|
|
89
|
+
| `web.password("secret")` | VΓ©rifie le mot de passe en session |
|
|
90
|
+
| `web.ip("192.168.1.1")` | VΓ©rifie l'IP du client (accepte aussi une liste) |
|
|
91
|
+
|
|
92
|
+
### Utilitaires
|
|
93
|
+
|
|
94
|
+
| Fonction | Description |
|
|
95
|
+
|----------|-------------|
|
|
96
|
+
| `web.env("VAR")` | Lit une variable d'environnement |
|
|
97
|
+
| `web.runapp(debug, port, host)` | DΓ©marre le serveur |
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## CLI
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
webforge new <nom> # CrΓ©e un nouveau projet
|
|
105
|
+
webforge run # Lance le projet courant
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Installation pour le dΓ©veloppement
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
git clone https://github.com/yourname/webforge
|
|
114
|
+
cd webforge
|
|
115
|
+
pip install -e .
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Licence
|
|
121
|
+
|
|
122
|
+
MIT
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
WebForge - Create web applications easily
|
|
3
|
+
|
|
4
|
+
Usage in forge.py:
|
|
5
|
+
from WebForge import webforge, security, env as web
|
|
6
|
+
|
|
7
|
+
@web.app("/")
|
|
8
|
+
def index():
|
|
9
|
+
web.route("style", "/public/service/style.css")
|
|
10
|
+
return web.render("index.html")
|
|
11
|
+
|
|
12
|
+
@web.security("/")
|
|
13
|
+
def secure_home():
|
|
14
|
+
if not web.password(pw): return web.redirect("/login")
|
|
15
|
+
if not web.ip(allowed): return web.abort(403)
|
|
16
|
+
|
|
17
|
+
web.runapp(debug=True, port=5000)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from .core import WebForgeApp
|
|
21
|
+
|
|
22
|
+
# Single global app instance shared across the whole project
|
|
23
|
+
_app = WebForgeApp()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class _Namespace:
|
|
27
|
+
"""
|
|
28
|
+
The WebForge namespace object.
|
|
29
|
+
Imported as `env as web` so that `web.env()`, `web.app()`, etc. all work.
|
|
30
|
+
Also callable directly: web("MY_VAR") == web.env("MY_VAR")
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, app: WebForgeApp):
|
|
34
|
+
self._app = app
|
|
35
|
+
|
|
36
|
+
def __call__(self, key: str, default=None):
|
|
37
|
+
return self._app.env(key, default)
|
|
38
|
+
|
|
39
|
+
# ββ Environment βββββββββββββββββββββββββββββββββββββ
|
|
40
|
+
def env(self, key: str, default=None):
|
|
41
|
+
"""Read an environment variable (like os.getenv)"""
|
|
42
|
+
return self._app.env(key, default)
|
|
43
|
+
|
|
44
|
+
# ββ Routing βββββββββββββββββββββββββββββββββββββββββ
|
|
45
|
+
def app(self, route_path: str, methods=None):
|
|
46
|
+
"""Decorator: register a route handler"""
|
|
47
|
+
return self._app.app(route_path, methods)
|
|
48
|
+
|
|
49
|
+
def route(self, name: str, path: str):
|
|
50
|
+
"""Register a named static file route"""
|
|
51
|
+
return self._app.add_static_route(name, path)
|
|
52
|
+
|
|
53
|
+
# ββ Responses βββββββββββββββββββββββββββββββββββββββ
|
|
54
|
+
def render(self, template: str, **context):
|
|
55
|
+
"""Render an HTML template from public/pages/"""
|
|
56
|
+
return self._app.render(template, **context)
|
|
57
|
+
|
|
58
|
+
def redirect(self, url: str, code: int = 302):
|
|
59
|
+
"""HTTP redirect"""
|
|
60
|
+
return self._app.redirect(url, code)
|
|
61
|
+
|
|
62
|
+
def abort(self, code: int, message=None):
|
|
63
|
+
"""Abort with an HTTP error code"""
|
|
64
|
+
return self._app.abort(code, message)
|
|
65
|
+
|
|
66
|
+
# ββ Security ββββββββββββββββββββββββββββββββββββββββ
|
|
67
|
+
def security(self, route_path: str):
|
|
68
|
+
"""Decorator: register security middleware for a route"""
|
|
69
|
+
return self._app.security(route_path)
|
|
70
|
+
|
|
71
|
+
def password(self, stored_password: str) -> bool:
|
|
72
|
+
"""Check session password against stored value"""
|
|
73
|
+
return self._app.check_password(stored_password)
|
|
74
|
+
|
|
75
|
+
def ip(self, allowed_ip) -> bool:
|
|
76
|
+
"""Check if request IP matches allowed value(s)"""
|
|
77
|
+
return self._app.check_ip(allowed_ip)
|
|
78
|
+
|
|
79
|
+
# ββ Server ββββββββββββββββββββββββββββββββββββββββββ
|
|
80
|
+
def runapp(self, debug: bool = False, port: int = 5000, host: str = "0.0.0.0"):
|
|
81
|
+
"""Start the WebForge development server"""
|
|
82
|
+
return self._app.run(debug=debug, port=port, host=host)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
_ns = _Namespace(_app)
|
|
86
|
+
|
|
87
|
+
# Public exports:
|
|
88
|
+
# from WebForge import webforge, security, env as web
|
|
89
|
+
webforge = _app # raw app instance (advanced use)
|
|
90
|
+
security = _ns.security # importable standalone decorator
|
|
91
|
+
env = _ns # becomes `web` via `env as web`
|
|
92
|
+
|
|
93
|
+
__all__ = ["WebForgeApp", "webforge", "security", "env"]
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""
|
|
2
|
+
WebForge Core - Main application engine
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import functools
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Callable, Optional, List, Union
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
from flask import (
|
|
13
|
+
Flask, request, session, redirect as flask_redirect,
|
|
14
|
+
abort as flask_abort, render_template_string, send_file,
|
|
15
|
+
make_response
|
|
16
|
+
)
|
|
17
|
+
FLASK_AVAILABLE = True
|
|
18
|
+
except ImportError:
|
|
19
|
+
FLASK_AVAILABLE = False
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class WebForgeResponse:
|
|
23
|
+
"""Wrapper for WebForge responses"""
|
|
24
|
+
def __init__(self, content, status=200, mimetype="text/html"):
|
|
25
|
+
self.content = content
|
|
26
|
+
self.status = status
|
|
27
|
+
self.mimetype = mimetype
|
|
28
|
+
self._is_redirect = False
|
|
29
|
+
self._is_abort = False
|
|
30
|
+
self._redirect_url = None
|
|
31
|
+
self._abort_code = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class WebForgeApp:
|
|
35
|
+
"""Main WebForge application class"""
|
|
36
|
+
|
|
37
|
+
def __init__(self):
|
|
38
|
+
self._routes = {} # route -> handler function
|
|
39
|
+
self._security = {} # route -> security function
|
|
40
|
+
self._static_routes = {} # name -> file path
|
|
41
|
+
self._template_dir = None
|
|
42
|
+
self._flask_app = None
|
|
43
|
+
self._project_root = None
|
|
44
|
+
|
|
45
|
+
def _find_project_root(self):
|
|
46
|
+
"""Auto-detect project root from calling script location"""
|
|
47
|
+
# Walk up from the forge.py location
|
|
48
|
+
frame = sys._getframe(2)
|
|
49
|
+
caller_file = frame.f_globals.get("__file__", "")
|
|
50
|
+
if caller_file:
|
|
51
|
+
# forge.py is in backend/server/, so project root is 2 levels up
|
|
52
|
+
p = Path(caller_file).resolve()
|
|
53
|
+
for _ in range(3):
|
|
54
|
+
p = p.parent
|
|
55
|
+
if (p / "public").exists():
|
|
56
|
+
return p
|
|
57
|
+
return Path(caller_file).resolve().parent.parent.parent
|
|
58
|
+
return Path.cwd()
|
|
59
|
+
|
|
60
|
+
def _get_flask_app(self):
|
|
61
|
+
if self._flask_app is None:
|
|
62
|
+
if not FLASK_AVAILABLE:
|
|
63
|
+
raise RuntimeError(
|
|
64
|
+
"Flask is required. Install it with: pip install flask"
|
|
65
|
+
)
|
|
66
|
+
if self._project_root is None:
|
|
67
|
+
self._project_root = Path.cwd()
|
|
68
|
+
|
|
69
|
+
template_folder = str(self._project_root / "public" / "pages")
|
|
70
|
+
static_folder = str(self._project_root / "public" / "service")
|
|
71
|
+
|
|
72
|
+
self._flask_app = Flask(
|
|
73
|
+
__name__,
|
|
74
|
+
template_folder=template_folder,
|
|
75
|
+
static_folder=static_folder,
|
|
76
|
+
static_url_path="/static"
|
|
77
|
+
)
|
|
78
|
+
self._flask_app.secret_key = os.urandom(32)
|
|
79
|
+
|
|
80
|
+
# Register all routes
|
|
81
|
+
self._register_all_routes()
|
|
82
|
+
|
|
83
|
+
return self._flask_app
|
|
84
|
+
|
|
85
|
+
def _register_all_routes(self):
|
|
86
|
+
flask_app = self._flask_app
|
|
87
|
+
|
|
88
|
+
for route_path, handler in self._routes.items():
|
|
89
|
+
security_fn = self._security.get(route_path)
|
|
90
|
+
self._register_flask_route(flask_app, route_path, handler, security_fn)
|
|
91
|
+
|
|
92
|
+
# Register named static routes
|
|
93
|
+
for name, file_path in self._static_routes.items():
|
|
94
|
+
self._register_static_file(flask_app, name, file_path)
|
|
95
|
+
|
|
96
|
+
def _register_flask_route(self, flask_app, route_path, handler, security_fn):
|
|
97
|
+
endpoint = f"webforge_{route_path.replace('/', '_').strip('_') or 'index'}"
|
|
98
|
+
|
|
99
|
+
@flask_app.route(route_path, methods=["GET", "POST", "PUT", "DELETE", "PATCH"])
|
|
100
|
+
@functools.wraps(handler)
|
|
101
|
+
def view_func():
|
|
102
|
+
# Run security checks first
|
|
103
|
+
if security_fn:
|
|
104
|
+
self._current_request_context = True
|
|
105
|
+
result = security_fn()
|
|
106
|
+
if result is not None:
|
|
107
|
+
return self._process_response(result)
|
|
108
|
+
|
|
109
|
+
# Run route handler
|
|
110
|
+
result = handler()
|
|
111
|
+
return self._process_response(result)
|
|
112
|
+
|
|
113
|
+
view_func.__name__ = endpoint
|
|
114
|
+
# Flask already registered it via decorator above
|
|
115
|
+
|
|
116
|
+
def _register_static_file(self, flask_app, name, file_path):
|
|
117
|
+
"""Register a named static file route"""
|
|
118
|
+
endpoint = f"static_{name}"
|
|
119
|
+
abs_path = self._project_root / file_path.lstrip("/")
|
|
120
|
+
|
|
121
|
+
@flask_app.route(f"/static/{name}")
|
|
122
|
+
def serve_static():
|
|
123
|
+
return send_file(str(abs_path))
|
|
124
|
+
|
|
125
|
+
serve_static.__name__ = endpoint
|
|
126
|
+
|
|
127
|
+
def _process_response(self, result):
|
|
128
|
+
"""Convert WebForge response to Flask response"""
|
|
129
|
+
if result is None:
|
|
130
|
+
return ("", 200)
|
|
131
|
+
|
|
132
|
+
if isinstance(result, str):
|
|
133
|
+
return result
|
|
134
|
+
|
|
135
|
+
if isinstance(result, WebForgeResponse):
|
|
136
|
+
if result._is_redirect:
|
|
137
|
+
return flask_redirect(result._redirect_url, result.status)
|
|
138
|
+
if result._is_abort:
|
|
139
|
+
flask_abort(result._abort_code)
|
|
140
|
+
return (result.content, result.status)
|
|
141
|
+
|
|
142
|
+
# Flask response object passthrough
|
|
143
|
+
return result
|
|
144
|
+
|
|
145
|
+
# βββ Public API βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
146
|
+
|
|
147
|
+
def env(self, key: str, default=None):
|
|
148
|
+
"""Get environment variable"""
|
|
149
|
+
return os.environ.get(key, default)
|
|
150
|
+
|
|
151
|
+
def app(self, route_path: str, methods: Optional[List[str]] = None):
|
|
152
|
+
"""Decorator to register a route"""
|
|
153
|
+
def decorator(fn: Callable):
|
|
154
|
+
self._routes[route_path] = fn
|
|
155
|
+
return fn
|
|
156
|
+
return decorator
|
|
157
|
+
|
|
158
|
+
def security(self, route_path: str):
|
|
159
|
+
"""Decorator to register security middleware for a route"""
|
|
160
|
+
def decorator(fn: Callable):
|
|
161
|
+
self._security[route_path] = fn
|
|
162
|
+
return fn
|
|
163
|
+
return decorator
|
|
164
|
+
|
|
165
|
+
def add_static_route(self, name: str, path: str):
|
|
166
|
+
"""Register a static file route"""
|
|
167
|
+
self._static_routes[name] = path
|
|
168
|
+
|
|
169
|
+
def render(self, template: str, **context):
|
|
170
|
+
"""Render an HTML template from public/pages/"""
|
|
171
|
+
from flask import render_template
|
|
172
|
+
return render_template(template, **context)
|
|
173
|
+
|
|
174
|
+
def redirect(self, url: str, code: int = 302):
|
|
175
|
+
"""Redirect to URL"""
|
|
176
|
+
return flask_redirect(url, code)
|
|
177
|
+
|
|
178
|
+
def abort(self, code: int, message: Optional[str] = None):
|
|
179
|
+
"""Abort with HTTP error"""
|
|
180
|
+
if message:
|
|
181
|
+
from flask import Response
|
|
182
|
+
return Response(message, status=code)
|
|
183
|
+
flask_abort(code)
|
|
184
|
+
|
|
185
|
+
def check_password(self, stored_password: str) -> bool:
|
|
186
|
+
"""Check if current session has matching password"""
|
|
187
|
+
return session.get("password") == stored_password
|
|
188
|
+
|
|
189
|
+
def check_ip(self, allowed_ip: Union[str, List[str]]) -> bool:
|
|
190
|
+
"""Check if request IP is in the allowed list"""
|
|
191
|
+
client_ip = request.remote_addr
|
|
192
|
+
if isinstance(allowed_ip, str):
|
|
193
|
+
return client_ip == allowed_ip
|
|
194
|
+
return client_ip in allowed_ip
|
|
195
|
+
|
|
196
|
+
def run(self, debug: bool = False, port: int = 5000, host: str = "0.0.0.0"):
|
|
197
|
+
"""Start the development server"""
|
|
198
|
+
# Detect project root from the script that called run()
|
|
199
|
+
frame = sys._getframe(1)
|
|
200
|
+
caller_file = frame.f_globals.get("__file__", "")
|
|
201
|
+
if caller_file:
|
|
202
|
+
p = Path(caller_file).resolve().parent
|
|
203
|
+
# Search upward for a dir that has public/
|
|
204
|
+
for _ in range(5):
|
|
205
|
+
if (p / "public").exists():
|
|
206
|
+
self._project_root = p
|
|
207
|
+
break
|
|
208
|
+
p = p.parent
|
|
209
|
+
else:
|
|
210
|
+
self._project_root = Path(caller_file).resolve().parent
|
|
211
|
+
|
|
212
|
+
flask_app = self._get_flask_app()
|
|
213
|
+
|
|
214
|
+
print(f"""
|
|
215
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
216
|
+
β π₯ WebForge Server β
|
|
217
|
+
β Running on http://{host}:{port} β
|
|
218
|
+
β Debug: {str(debug):<37}β
|
|
219
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
220
|
+
""")
|
|
221
|
+
|
|
222
|
+
flask_app.run(debug=debug, port=port, host=host)
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: WebForge
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: π₯ Create web applications easily β a Flask-powered micro-framework
|
|
5
|
+
Home-page: https://github.com/yourname/webforge
|
|
6
|
+
Author: Your Name
|
|
7
|
+
Author-email: you@example.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Homepage, https://github.com/yourname/webforge
|
|
10
|
+
Project-URL: Repository, https://github.com/yourname/webforge
|
|
11
|
+
Project-URL: Issues, https://github.com/yourname/webforge/issues
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: flask>=3.0.0
|
|
15
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
16
|
+
Dynamic: author
|
|
17
|
+
Dynamic: author-email
|
|
18
|
+
Dynamic: home-page
|
|
19
|
+
Dynamic: requires-python
|
|
20
|
+
|
|
21
|
+
# π₯ WebForge
|
|
22
|
+
|
|
23
|
+
**Create web applications easily** β a minimal Flask-powered framework with clean syntax.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install WebForge
|
|
27
|
+
webforge new mon-site
|
|
28
|
+
cd mon-site
|
|
29
|
+
python backend/server/forge.py
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Structure d'un projet WebForge
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
mon-site/
|
|
38
|
+
βββ backend/
|
|
39
|
+
β βββ requirements.txt
|
|
40
|
+
β βββ server/
|
|
41
|
+
β βββ forge.py β CΕur du serveur
|
|
42
|
+
βββ public/
|
|
43
|
+
βββ pages/
|
|
44
|
+
β βββ index.html β Templates HTML
|
|
45
|
+
βββ script/
|
|
46
|
+
β βββ app.js β JavaScript client
|
|
47
|
+
βββ service/
|
|
48
|
+
βββ style.css β Fichiers statiques
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Γcrire `forge.py`
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from WebForge import webforge, security, env as web
|
|
57
|
+
|
|
58
|
+
# Variables d'environnement
|
|
59
|
+
password = web.env("PASSWORD")
|
|
60
|
+
ip = "192.168.1.123"
|
|
61
|
+
|
|
62
|
+
# βββ Routes ββββββββββββββββββββββββββββββββββββββ
|
|
63
|
+
@web.app("/")
|
|
64
|
+
def index():
|
|
65
|
+
web.route("style", "/public/service/style.css")
|
|
66
|
+
return web.render("index.html")
|
|
67
|
+
|
|
68
|
+
@web.app("/login")
|
|
69
|
+
def login():
|
|
70
|
+
return web.render("login.html")
|
|
71
|
+
|
|
72
|
+
# βββ SΓ©curitΓ© ββββββββββββββββββββββββββββββββββββ
|
|
73
|
+
@web.security("/")
|
|
74
|
+
def secure_home():
|
|
75
|
+
if not web.password(password): # vΓ©rif mot de passe en session
|
|
76
|
+
return web.redirect("/login")
|
|
77
|
+
if not web.ip(ip): # vΓ©rif IP
|
|
78
|
+
return web.abort(403)
|
|
79
|
+
|
|
80
|
+
# βββ DΓ©marrage βββββββββββββββββββββββββββββββββββ
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
web.runapp(debug=True, port=5000, host="0.0.0.0")
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## API complète
|
|
88
|
+
|
|
89
|
+
### Routes
|
|
90
|
+
|
|
91
|
+
| Fonction | Description |
|
|
92
|
+
|----------|-------------|
|
|
93
|
+
| `@web.app("/path")` | DΓ©finit une route |
|
|
94
|
+
| `@web.security("/path")` | Middleware de sΓ©curitΓ© pour une route |
|
|
95
|
+
| `web.route("name", "/path/to/file")` | Route vers un fichier statique |
|
|
96
|
+
|
|
97
|
+
### RΓ©ponses
|
|
98
|
+
|
|
99
|
+
| Fonction | Description |
|
|
100
|
+
|----------|-------------|
|
|
101
|
+
| `web.render("page.html", **ctx)` | Rend un template HTML |
|
|
102
|
+
| `web.redirect("/url")` | Redirige vers une URL |
|
|
103
|
+
| `web.abort(403)` | Retourne une erreur HTTP |
|
|
104
|
+
|
|
105
|
+
### SΓ©curitΓ©
|
|
106
|
+
|
|
107
|
+
| Fonction | Description |
|
|
108
|
+
|----------|-------------|
|
|
109
|
+
| `web.password("secret")` | VΓ©rifie le mot de passe en session |
|
|
110
|
+
| `web.ip("192.168.1.1")` | VΓ©rifie l'IP du client (accepte aussi une liste) |
|
|
111
|
+
|
|
112
|
+
### Utilitaires
|
|
113
|
+
|
|
114
|
+
| Fonction | Description |
|
|
115
|
+
|----------|-------------|
|
|
116
|
+
| `web.env("VAR")` | Lit une variable d'environnement |
|
|
117
|
+
| `web.runapp(debug, port, host)` | DΓ©marre le serveur |
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## CLI
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
webforge new <nom> # CrΓ©e un nouveau projet
|
|
125
|
+
webforge run # Lance le projet courant
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Installation pour le dΓ©veloppement
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
git clone https://github.com/yourname/webforge
|
|
134
|
+
cd webforge
|
|
135
|
+
pip install -e .
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Licence
|
|
141
|
+
|
|
142
|
+
MIT
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.py
|
|
4
|
+
WebForge/__init__.py
|
|
5
|
+
WebForge/core.py
|
|
6
|
+
WebForge.egg-info/PKG-INFO
|
|
7
|
+
WebForge.egg-info/SOURCES.txt
|
|
8
|
+
WebForge.egg-info/dependency_links.txt
|
|
9
|
+
WebForge.egg-info/entry_points.txt
|
|
10
|
+
WebForge.egg-info/requires.txt
|
|
11
|
+
WebForge.egg-info/top_level.txt
|
|
12
|
+
WebForge_cli/cli.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
WebForge CLI - Scaffold new projects
|
|
4
|
+
Usage: webforge new <project-name>
|
|
5
|
+
webforge run
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
import os
|
|
10
|
+
import argparse
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
FORGE_PY_TEMPLATE = '''\
|
|
15
|
+
from WebForge import webforge, security, env as web
|
|
16
|
+
|
|
17
|
+
# βββ Environment βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
18
|
+
# password = web.env("PASSWORD")
|
|
19
|
+
# ip = "192.168.1.1"
|
|
20
|
+
|
|
21
|
+
# βββ Routes ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
22
|
+
@web.app("/")
|
|
23
|
+
def index():
|
|
24
|
+
web.route("style", "/public/service/style.css")
|
|
25
|
+
return web.render("index.html")
|
|
26
|
+
|
|
27
|
+
@web.app("/login")
|
|
28
|
+
def login():
|
|
29
|
+
return web.render("login.html")
|
|
30
|
+
|
|
31
|
+
# βββ Security ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
32
|
+
# @web.security("/")
|
|
33
|
+
# def secure_home():
|
|
34
|
+
# if not web.password(password):
|
|
35
|
+
# return web.redirect("/login")
|
|
36
|
+
# if not web.ip(ip):
|
|
37
|
+
# return web.abort(403)
|
|
38
|
+
|
|
39
|
+
# βββ Run βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
40
|
+
if __name__ == "__main__":
|
|
41
|
+
web.runapp(debug=True, port=5000, host="0.0.0.0")
|
|
42
|
+
'''
|
|
43
|
+
|
|
44
|
+
INDEX_HTML_TEMPLATE = '''\
|
|
45
|
+
<!DOCTYPE html>
|
|
46
|
+
<html lang="en">
|
|
47
|
+
<head>
|
|
48
|
+
<meta charset="UTF-8" />
|
|
49
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
50
|
+
<title>WebForge App</title>
|
|
51
|
+
<link rel="stylesheet" href="/static/style" />
|
|
52
|
+
</head>
|
|
53
|
+
<body>
|
|
54
|
+
<main>
|
|
55
|
+
<h1>π₯ Welcome to WebForge</h1>
|
|
56
|
+
<p>Your app is running. Edit <code>public/pages/index.html</code> to get started.</p>
|
|
57
|
+
<a href="/login">Go to Login</a>
|
|
58
|
+
</main>
|
|
59
|
+
<script src="/public/script/app.js"></script>
|
|
60
|
+
</body>
|
|
61
|
+
</html>
|
|
62
|
+
'''
|
|
63
|
+
|
|
64
|
+
LOGIN_HTML_TEMPLATE = '''\
|
|
65
|
+
<!DOCTYPE html>
|
|
66
|
+
<html lang="en">
|
|
67
|
+
<head>
|
|
68
|
+
<meta charset="UTF-8" />
|
|
69
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
70
|
+
<title>Login - WebForge</title>
|
|
71
|
+
<link rel="stylesheet" href="/static/style" />
|
|
72
|
+
</head>
|
|
73
|
+
<body>
|
|
74
|
+
<main>
|
|
75
|
+
<h1>π Login</h1>
|
|
76
|
+
<form method="POST" action="/login">
|
|
77
|
+
<input type="password" name="password" placeholder="Password" required />
|
|
78
|
+
<button type="submit">Sign In</button>
|
|
79
|
+
</form>
|
|
80
|
+
</main>
|
|
81
|
+
</body>
|
|
82
|
+
</html>
|
|
83
|
+
'''
|
|
84
|
+
|
|
85
|
+
STYLE_CSS_TEMPLATE = '''\
|
|
86
|
+
/* WebForge default styles */
|
|
87
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
88
|
+
|
|
89
|
+
body {
|
|
90
|
+
font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
|
|
91
|
+
background: #0f0f13;
|
|
92
|
+
color: #e8e8f0;
|
|
93
|
+
min-height: 100vh;
|
|
94
|
+
display: flex;
|
|
95
|
+
align-items: center;
|
|
96
|
+
justify-content: center;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
main {
|
|
100
|
+
text-align: center;
|
|
101
|
+
padding: 3rem;
|
|
102
|
+
max-width: 600px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
h1 {
|
|
106
|
+
font-size: 2.5rem;
|
|
107
|
+
margin-bottom: 1rem;
|
|
108
|
+
background: linear-gradient(135deg, #ff6b35, #f7c59f);
|
|
109
|
+
-webkit-background-clip: text;
|
|
110
|
+
-webkit-text-fill-color: transparent;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
p { color: #a0a0b8; margin-bottom: 1.5rem; }
|
|
114
|
+
|
|
115
|
+
a, button {
|
|
116
|
+
display: inline-block;
|
|
117
|
+
padding: 0.75rem 2rem;
|
|
118
|
+
background: #ff6b35;
|
|
119
|
+
color: white;
|
|
120
|
+
border: none;
|
|
121
|
+
border-radius: 8px;
|
|
122
|
+
text-decoration: none;
|
|
123
|
+
cursor: pointer;
|
|
124
|
+
font-size: 1rem;
|
|
125
|
+
transition: background 0.2s;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
a:hover, button:hover { background: #e85a25; }
|
|
129
|
+
|
|
130
|
+
input {
|
|
131
|
+
display: block;
|
|
132
|
+
width: 100%;
|
|
133
|
+
padding: 0.75rem 1rem;
|
|
134
|
+
margin-bottom: 1rem;
|
|
135
|
+
background: #1a1a24;
|
|
136
|
+
border: 1px solid #333;
|
|
137
|
+
border-radius: 8px;
|
|
138
|
+
color: #e8e8f0;
|
|
139
|
+
font-size: 1rem;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
code {
|
|
143
|
+
background: #1a1a24;
|
|
144
|
+
padding: 0.2rem 0.5rem;
|
|
145
|
+
border-radius: 4px;
|
|
146
|
+
font-family: monospace;
|
|
147
|
+
color: #ff6b35;
|
|
148
|
+
}
|
|
149
|
+
'''
|
|
150
|
+
|
|
151
|
+
APP_JS_TEMPLATE = '''\
|
|
152
|
+
// WebForge App - client-side scripts
|
|
153
|
+
console.log("π₯ WebForge App loaded");
|
|
154
|
+
'''
|
|
155
|
+
|
|
156
|
+
REQUIREMENTS_TEMPLATE = '''\
|
|
157
|
+
WebForge
|
|
158
|
+
flask>=3.0.0
|
|
159
|
+
python-dotenv>=1.0.0
|
|
160
|
+
'''
|
|
161
|
+
|
|
162
|
+
GITIGNORE_TEMPLATE = '''\
|
|
163
|
+
__pycache__/
|
|
164
|
+
*.py[cod]
|
|
165
|
+
.env
|
|
166
|
+
*.env
|
|
167
|
+
venv/
|
|
168
|
+
.venv/
|
|
169
|
+
'''
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def scaffold(name: str):
|
|
173
|
+
base = Path(name)
|
|
174
|
+
if base.exists():
|
|
175
|
+
print(f"β Directory '{name}' already exists.")
|
|
176
|
+
sys.exit(1)
|
|
177
|
+
|
|
178
|
+
dirs = [
|
|
179
|
+
base / "backend" / "server",
|
|
180
|
+
base / "public" / "pages",
|
|
181
|
+
base / "public" / "script",
|
|
182
|
+
base / "public" / "service",
|
|
183
|
+
]
|
|
184
|
+
for d in dirs:
|
|
185
|
+
d.mkdir(parents=True)
|
|
186
|
+
|
|
187
|
+
files = {
|
|
188
|
+
base / "backend" / "server" / "forge.py": FORGE_PY_TEMPLATE,
|
|
189
|
+
base / "backend" / "requirements.txt": REQUIREMENTS_TEMPLATE,
|
|
190
|
+
base / "public" / "pages" / "index.html": INDEX_HTML_TEMPLATE,
|
|
191
|
+
base / "public" / "pages" / "login.html": LOGIN_HTML_TEMPLATE,
|
|
192
|
+
base / "public" / "service" / "style.css": STYLE_CSS_TEMPLATE,
|
|
193
|
+
base / "public" / "script" / "app.js": APP_JS_TEMPLATE,
|
|
194
|
+
base / ".gitignore": GITIGNORE_TEMPLATE,
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
for path, content in files.items():
|
|
198
|
+
path.write_text(content, encoding="utf-8")
|
|
199
|
+
|
|
200
|
+
print(f"""
|
|
201
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
202
|
+
β π₯ WebForge Project Created! β
|
|
203
|
+
β βββββββββββββββββββββββββββββββββββββββββββββββ£
|
|
204
|
+
β Project: {name:<35}β
|
|
205
|
+
β βββββββββββββββββββββββββββββββββββββββββββββββ£
|
|
206
|
+
β Next steps: β
|
|
207
|
+
β cd {name:<39}β
|
|
208
|
+
β pip install -r backend/requirements.txt β
|
|
209
|
+
β python backend/server/forge.py β
|
|
210
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
211
|
+
""")
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def run_server():
|
|
215
|
+
"""Run the forge.py in the current project"""
|
|
216
|
+
forge = Path("backend/server/forge.py")
|
|
217
|
+
if not forge.exists():
|
|
218
|
+
print("β No forge.py found. Are you in a WebForge project directory?")
|
|
219
|
+
sys.exit(1)
|
|
220
|
+
os.execv(sys.executable, [sys.executable, str(forge)])
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def main():
|
|
224
|
+
parser = argparse.ArgumentParser(
|
|
225
|
+
prog="webforge",
|
|
226
|
+
description="π₯ WebForge - Create web apps easily"
|
|
227
|
+
)
|
|
228
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
229
|
+
|
|
230
|
+
# webforge new <name>
|
|
231
|
+
new_parser = subparsers.add_parser("new", help="Create a new WebForge project")
|
|
232
|
+
new_parser.add_argument("name", help="Project name")
|
|
233
|
+
|
|
234
|
+
# webforge run
|
|
235
|
+
subparsers.add_parser("run", help="Run the current WebForge project")
|
|
236
|
+
|
|
237
|
+
args = parser.parse_args()
|
|
238
|
+
|
|
239
|
+
if args.command == "new":
|
|
240
|
+
scaffold(args.name)
|
|
241
|
+
elif args.command == "run":
|
|
242
|
+
run_server()
|
|
243
|
+
else:
|
|
244
|
+
parser.print_help()
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
if __name__ == "__main__":
|
|
248
|
+
main()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "WebForge"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "π₯ Create web applications easily β a Flask-powered micro-framework"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
dependencies = [
|
|
13
|
+
"flask>=3.0.0",
|
|
14
|
+
"python-dotenv>=1.0.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.scripts]
|
|
18
|
+
webforge = "webforge_cli.cli:main"
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://github.com/yourname/webforge"
|
|
22
|
+
Repository = "https://github.com/yourname/webforge"
|
|
23
|
+
Issues = "https://github.com/yourname/webforge/issues"
|
|
24
|
+
|
|
25
|
+
[tool.setuptools.packages.find]
|
|
26
|
+
include = ["WebForge*", "webforge_cli*"]
|
webforge-1.0.0/setup.cfg
ADDED
webforge-1.0.0/setup.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r", encoding="utf-8") as f:
|
|
4
|
+
long_description = f.read()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name="WebForge",
|
|
8
|
+
version="1.0.0",
|
|
9
|
+
author="Your Name",
|
|
10
|
+
author_email="you@example.com",
|
|
11
|
+
description="π₯ Create web applications easily β a Flask-powered micro-framework",
|
|
12
|
+
long_description=long_description,
|
|
13
|
+
long_description_content_type="text/markdown",
|
|
14
|
+
url="https://github.com/yourname/webforge",
|
|
15
|
+
packages=find_packages(),
|
|
16
|
+
include_package_data=True,
|
|
17
|
+
python_requires=">=3.8",
|
|
18
|
+
install_requires=[
|
|
19
|
+
"flask>=3.0.0",
|
|
20
|
+
"python-dotenv>=1.0.0",
|
|
21
|
+
],
|
|
22
|
+
entry_points={
|
|
23
|
+
"console_scripts": [
|
|
24
|
+
"webforge=webforge_cli.cli:main",
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
classifiers=[
|
|
28
|
+
"Development Status :: 4 - Beta",
|
|
29
|
+
"Intended Audience :: Developers",
|
|
30
|
+
"License :: OSI Approved :: MIT License",
|
|
31
|
+
"Programming Language :: Python :: 3",
|
|
32
|
+
"Programming Language :: Python :: 3.8",
|
|
33
|
+
"Programming Language :: Python :: 3.9",
|
|
34
|
+
"Programming Language :: Python :: 3.10",
|
|
35
|
+
"Programming Language :: Python :: 3.11",
|
|
36
|
+
"Programming Language :: Python :: 3.12",
|
|
37
|
+
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
|
|
38
|
+
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
39
|
+
],
|
|
40
|
+
keywords="web framework flask simple easy webforge",
|
|
41
|
+
)
|