Nexom 0.1.3__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.
- nexom/__init__.py +19 -0
- nexom/__main__.py +61 -0
- nexom/assets/error_page/error.html +44 -0
- nexom/assets/server/config.py +27 -0
- nexom/assets/server/gunicorn.conf.py +16 -0
- nexom/assets/server/pages/__init__.py +3 -0
- nexom/assets/server/pages/__pycache__/__init__.cpython-313.pyc +0 -0
- nexom/assets/server/pages/_templates.py +11 -0
- nexom/assets/server/pages/default.py +10 -0
- nexom/assets/server/pages/document.py +10 -0
- nexom/assets/server/router.py +18 -0
- nexom/assets/server/static/dog.jpeg +0 -0
- nexom/assets/server/static/style.css +39 -0
- nexom/assets/server/templates/base.html +18 -0
- nexom/assets/server/templates/default.html +7 -0
- nexom/assets/server/templates/document.html +169 -0
- nexom/assets/server/templates/footer.html +3 -0
- nexom/assets/server/templates/header.html +3 -0
- nexom/assets/server/wsgi.py +30 -0
- nexom/buildTools/__init__.py +0 -0
- nexom/buildTools/build.py +99 -0
- nexom/core/__init__.py +1 -0
- nexom/core/error.py +149 -0
- nexom/engine/__init__.py +1 -0
- nexom/engine/object_html_render.py +224 -0
- nexom/web/__init__.py +5 -0
- nexom/web/cookie.py +73 -0
- nexom/web/http_status_codes.py +72 -0
- nexom/web/middleware.py +51 -0
- nexom/web/path.py +125 -0
- nexom/web/request.py +62 -0
- nexom/web/response.py +146 -0
- nexom/web/template.py +115 -0
- nexom-0.1.3.dist-info/METADATA +168 -0
- nexom-0.1.3.dist-info/RECORD +39 -0
- nexom-0.1.3.dist-info/WHEEL +5 -0
- nexom-0.1.3.dist-info/entry_points.txt +2 -0
- nexom-0.1.3.dist-info/licenses/LICENSE +21 -0
- nexom-0.1.3.dist-info/top_level.txt +1 -0
nexom/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NEXOM - A lightweight Python web framework.
|
|
3
|
+
|
|
4
|
+
NEXOM provides a simple and flexible foundation for building
|
|
5
|
+
WSGI-based web applications with minimal overhead.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from nexom.web.request import Request
|
|
11
|
+
from nexom.web.response import Response
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"Request",
|
|
15
|
+
"Response",
|
|
16
|
+
"__version__",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
__version__ = "0.1.2"
|
nexom/__main__.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from nexom.buildTools.build import server as build_server
|
|
8
|
+
from nexom.buildTools.build import ServerBuildOptions
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main(argv: list[str] | None = None) -> None:
|
|
12
|
+
parser = argparse.ArgumentParser(
|
|
13
|
+
prog="nexom",
|
|
14
|
+
description="Nexom Web Framework CLI",
|
|
15
|
+
)
|
|
16
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
17
|
+
|
|
18
|
+
# test
|
|
19
|
+
subparsers.add_parser("test", help="Test Nexom installation")
|
|
20
|
+
|
|
21
|
+
# build-server
|
|
22
|
+
p = subparsers.add_parser(
|
|
23
|
+
"build-server",
|
|
24
|
+
help="Create a Nexom server project",
|
|
25
|
+
)
|
|
26
|
+
p.add_argument("server_name", help="Server project name")
|
|
27
|
+
p.add_argument(
|
|
28
|
+
"--out",
|
|
29
|
+
default=".",
|
|
30
|
+
help="Output directory (default: current directory)",
|
|
31
|
+
)
|
|
32
|
+
p.add_argument("--address", default="0.0.0.0", help="Bind address (default: 0.0.0.0)")
|
|
33
|
+
p.add_argument("--port", type=int, default=8080, help="Bind port (default: 8080)")
|
|
34
|
+
p.add_argument("--workers", type=int, default=4, help="Gunicorn workers (default: 4)")
|
|
35
|
+
p.add_argument("--reload", action="store_true", help="Enable auto-reload (development)")
|
|
36
|
+
|
|
37
|
+
args = parser.parse_args(argv)
|
|
38
|
+
|
|
39
|
+
if args.command == "test":
|
|
40
|
+
print("Hello Nexom Web Framework!")
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
if args.command == "build-server":
|
|
44
|
+
options = ServerBuildOptions(
|
|
45
|
+
address=args.address,
|
|
46
|
+
port=args.port,
|
|
47
|
+
workers=args.workers,
|
|
48
|
+
reload=args.reload,
|
|
49
|
+
)
|
|
50
|
+
out_dir = build_server(Path(args.out), args.server_name, options=options)
|
|
51
|
+
print(f"Created Nexom server project at: {out_dir}")
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__":
|
|
56
|
+
try:
|
|
57
|
+
main()
|
|
58
|
+
except Exception as e:
|
|
59
|
+
# CLI では stacktrace よりまずメッセージ優先(必要なら後で --verbose とか足す)
|
|
60
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
61
|
+
sys.exit(1)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="ja">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>__STATUS__</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div class="error_container">
|
|
10
|
+
<div class="error_name">__STATUS__</div>
|
|
11
|
+
<div class="error_message">__MESSAGE__</div>
|
|
12
|
+
</div>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
|
15
|
+
<style>
|
|
16
|
+
@import url('https://fonts.googleapis.com/css2?family=Google+Sans:ital,opsz,wght@0,17..18,400..700;1,17..18,400..700&family=Noto+Sans+JP:wght@100..900&display=swap');
|
|
17
|
+
body{
|
|
18
|
+
position: relative;
|
|
19
|
+
width: 100%;
|
|
20
|
+
min-width: 300px;
|
|
21
|
+
height: 100vh;
|
|
22
|
+
display: flex;
|
|
23
|
+
justify-content: center;
|
|
24
|
+
align-items: center;
|
|
25
|
+
margin: 0;
|
|
26
|
+
|
|
27
|
+
font-family: 'Noto Sans JP', 'Hiragino Sans', 'ヒラギノ角ゴシック', 'Yu Gothic','游ゴシック';
|
|
28
|
+
background: linear-gradient(135deg, #ffffff, 90%, #ff0050);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.error_container{
|
|
32
|
+
position: relative;
|
|
33
|
+
padding: 30px;
|
|
34
|
+
}
|
|
35
|
+
.error_name{
|
|
36
|
+
font-size: 2rem;
|
|
37
|
+
font-family: "Google Sans";
|
|
38
|
+
color: rgb(246, 60, 60);
|
|
39
|
+
}
|
|
40
|
+
.error_message{
|
|
41
|
+
font-size: 1rem;
|
|
42
|
+
font-family: "Google Sans";
|
|
43
|
+
}
|
|
44
|
+
</style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Nexom server settings.
|
|
3
|
+
|
|
4
|
+
This file is generated by Nexom buildTools.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# ======== module ========
|
|
13
|
+
# Project root directory
|
|
14
|
+
directory: str = "{pwd_dir}"
|
|
15
|
+
|
|
16
|
+
# ======== gunicorn ========
|
|
17
|
+
_address: str = "{g_address}"
|
|
18
|
+
_port: int = {g_port}
|
|
19
|
+
_workers: int = {g_workers}
|
|
20
|
+
_reload: bool = {g_reload}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def project_path(*parts: str) -> str:
|
|
24
|
+
"""
|
|
25
|
+
Build an absolute path under the project directory.
|
|
26
|
+
"""
|
|
27
|
+
return str(Path(directory, *parts))
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
# Ensure config.py in the same directory is importable
|
|
8
|
+
ROOT = Path(__file__).resolve().parent
|
|
9
|
+
if str(ROOT) not in sys.path:
|
|
10
|
+
sys.path.insert(0, str(ROOT))
|
|
11
|
+
|
|
12
|
+
from config import _address, _port, _workers, _reload # noqa: E402
|
|
13
|
+
|
|
14
|
+
bind = f"{_address}:{_port}"
|
|
15
|
+
workers = int(_workers)
|
|
16
|
+
reload = bool(_reload)
|
|
Binary file
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from nexom.web.template import ObjectHTMLTemplates
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# templates/ directory is located at: <project_root>/templates
|
|
9
|
+
TEMPLATES_DIR = (Path(__file__).resolve().parent.parent / "templates").resolve()
|
|
10
|
+
|
|
11
|
+
templates = ObjectHTMLTemplates(base_dir=str(TEMPLATES_DIR))
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pathlib as plib
|
|
4
|
+
|
|
5
|
+
from nexom.web.path import Path, Static, Pathlib
|
|
6
|
+
|
|
7
|
+
from pages import default, document
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Project root directory (where this file exists)
|
|
11
|
+
ROOT = plib.Path(__file__).resolve().parent
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
routing = Pathlib(
|
|
15
|
+
Path("", default.main, "DefaultPage"),
|
|
16
|
+
Path("doc/", document.main, "DocumentPage"),
|
|
17
|
+
Static("static/", str(ROOT / "static"), "StaticFiles"),
|
|
18
|
+
)
|
|
Binary file
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
body{
|
|
2
|
+
width: 100%;
|
|
3
|
+
padding: 0;
|
|
4
|
+
margin: 0;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
header{
|
|
8
|
+
width: 100%;
|
|
9
|
+
text-align: center;
|
|
10
|
+
padding-block: 1rem;
|
|
11
|
+
margin-block-end: 1rem;
|
|
12
|
+
}
|
|
13
|
+
.header-title{
|
|
14
|
+
font-size: 20px;
|
|
15
|
+
font-weight: 500;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
main{
|
|
19
|
+
position: relative;
|
|
20
|
+
width: 600px;
|
|
21
|
+
max-width: 100%;
|
|
22
|
+
margin-inline: auto;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
footer{
|
|
26
|
+
width: 100%;
|
|
27
|
+
text-align: center;
|
|
28
|
+
padding-block: 1rem;
|
|
29
|
+
margin-block-start: 3rem;
|
|
30
|
+
}
|
|
31
|
+
.footer-text{
|
|
32
|
+
font-size: 0.9rem;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
pre{
|
|
36
|
+
padding: 0.4rem 0.8rem;
|
|
37
|
+
background-color: rgb(239 239 239);
|
|
38
|
+
border-radius: 0.4rem;
|
|
39
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="ja">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>{{ title }}</title>
|
|
7
|
+
<link rel="stylesheet" href="/static/style.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<Import header />
|
|
11
|
+
|
|
12
|
+
<main>
|
|
13
|
+
{{ main }}
|
|
14
|
+
</main>
|
|
15
|
+
|
|
16
|
+
<Import footer />
|
|
17
|
+
</body>
|
|
18
|
+
</html>
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
<Extends base />
|
|
2
|
+
<Insert main>
|
|
3
|
+
<section id="install">
|
|
4
|
+
<h2>インストール</h2>
|
|
5
|
+
<pre><code>pip install nexom</code></pre>
|
|
6
|
+
<div class="note">
|
|
7
|
+
<strong>前提:</strong> Nexom は WSGI ベースです。WSGI サーバ(例: gunicorn)と組み合わせて動かす想定です。
|
|
8
|
+
</div>
|
|
9
|
+
</section>
|
|
10
|
+
|
|
11
|
+
<section id="quickstart">
|
|
12
|
+
<h2>最短スタート</h2>
|
|
13
|
+
<p>まずは「HTML を返すルート」を 1 本作るのが最速です。</p>
|
|
14
|
+
<h3>ルート定義(例)</h3>
|
|
15
|
+
<pre><code>from nexom.web.path import Path, Pathlib
|
|
16
|
+
from nexom.web.response import HtmlResponse
|
|
17
|
+
|
|
18
|
+
def index(request, args):
|
|
19
|
+
return HtmlResponse("<h1>Hello Nexom</h1>")
|
|
20
|
+
|
|
21
|
+
routing = Pathlib(
|
|
22
|
+
Path("", index, "index"),
|
|
23
|
+
)</code></pre>
|
|
24
|
+
|
|
25
|
+
<p class="note">
|
|
26
|
+
<strong>ポイント:</strong> Nexom の handler は <code>(request, args)</code> を受け取って <code>Response</code>(または <code>HtmlResponse</code>/<code>JsonResponse</code>)を返すのが基本です。
|
|
27
|
+
</p>
|
|
28
|
+
</section>
|
|
29
|
+
|
|
30
|
+
<section id="routing">
|
|
31
|
+
<h2>ルーティング</h2>
|
|
32
|
+
|
|
33
|
+
<h3>基本</h3>
|
|
34
|
+
<pre><code>from nexom.web.path import Path, Pathlib
|
|
35
|
+
|
|
36
|
+
routing = Pathlib(
|
|
37
|
+
Path("", index, "index"),
|
|
38
|
+
Path("doc/", docs, "docs"),
|
|
39
|
+
)</code></pre>
|
|
40
|
+
|
|
41
|
+
<h3>パスパラメータ({id})</h3>
|
|
42
|
+
<p><code>user/{id}</code> のように書くと、URL の該当部分が <code>args</code> に入ります。</p>
|
|
43
|
+
<pre><code>from nexom.web.response import HtmlResponse
|
|
44
|
+
|
|
45
|
+
def user(request, args):
|
|
46
|
+
return HtmlResponse(f"<p>User ID: {args['id']}</p>")
|
|
47
|
+
|
|
48
|
+
Path("user/{id}", user, "user")</code></pre>
|
|
49
|
+
|
|
50
|
+
<h3>静的ファイル(Static)</h3>
|
|
51
|
+
<p><code>Static</code> を使うと、ディレクトリ配下のファイルを配信できます。</p>
|
|
52
|
+
<pre><code>from nexom.web.path import Static
|
|
53
|
+
|
|
54
|
+
Static("static/", "./static", "static_files")</code></pre>
|
|
55
|
+
<ul>
|
|
56
|
+
<li><code>/static/xxx</code> を <code>./static/xxx</code> にマップして配信します。</li>
|
|
57
|
+
<li>ディレクトリトラバーサル対策(<code>../</code>)はフレームワーク側でブロックする設計です。</li>
|
|
58
|
+
</ul>
|
|
59
|
+
</section>
|
|
60
|
+
|
|
61
|
+
<section id="responses">
|
|
62
|
+
<h2>レスポンス</h2>
|
|
63
|
+
|
|
64
|
+
<h3>HtmlResponse</h3>
|
|
65
|
+
<pre><code>from nexom.web.response import HtmlResponse
|
|
66
|
+
|
|
67
|
+
return HtmlResponse("<h1>OK</h1>")</code></pre>
|
|
68
|
+
|
|
69
|
+
<h3>JsonResponse</h3>
|
|
70
|
+
<pre><code>from nexom.web.response import JsonResponse
|
|
71
|
+
|
|
72
|
+
return JsonResponse({"ok": True, "message": "hello"})</code></pre>
|
|
73
|
+
|
|
74
|
+
<h3>低レベル Response(自由度重視)</h3>
|
|
75
|
+
<p>高度なことをしたい場合は <code>Response</code> を直接使います。</p>
|
|
76
|
+
<pre><code>from nexom.web.response import Response
|
|
77
|
+
|
|
78
|
+
# バイナリや任意 Content-Type を返す
|
|
79
|
+
return Response(
|
|
80
|
+
b"binary",
|
|
81
|
+
content_type="application/octet-stream",
|
|
82
|
+
)</code></pre>
|
|
83
|
+
|
|
84
|
+
<h3>リダイレクト</h3>
|
|
85
|
+
<pre><code>from nexom.web.response import Redirect
|
|
86
|
+
|
|
87
|
+
return Redirect("/to")</code></pre>
|
|
88
|
+
|
|
89
|
+
<h3>エラーレスポンス</h3>
|
|
90
|
+
<p>標準のエラーページテンプレートから HTML を生成します。</p>
|
|
91
|
+
<pre><code>from nexom.web.response import ErrorResponse
|
|
92
|
+
|
|
93
|
+
return ErrorResponse(404, "Not Found")</code></pre>
|
|
94
|
+
|
|
95
|
+
<div class="note">
|
|
96
|
+
<strong>charset について:</strong>
|
|
97
|
+
Nexom は <code>str</code> の body を返すときに <code>charset</code> で <code>encode</code> します。<br>
|
|
98
|
+
<code>HtmlResponse</code> / <code>JsonResponse</code> は “短く書ける” 方向で、UTF-8 を前提に使える設計です。
|
|
99
|
+
</div>
|
|
100
|
+
</section>
|
|
101
|
+
|
|
102
|
+
<section id="templates">
|
|
103
|
+
<h2>テンプレート</h2>
|
|
104
|
+
<p>Nexom のテンプレートは「最小機能で予測可能」に寄せた独自構文です。</p>
|
|
105
|
+
|
|
106
|
+
<h3>変数展開</h3>
|
|
107
|
+
<pre><code><h1>{{ title }}</h1></code></pre>
|
|
108
|
+
|
|
109
|
+
<h3>継承(Extends)と差し込み(Insert)</h3>
|
|
110
|
+
<pre><code><Extends base />
|
|
111
|
+
<Insert main>
|
|
112
|
+
<h1>Hello</h1>
|
|
113
|
+
</Insert></code></pre>
|
|
114
|
+
|
|
115
|
+
<h3>部品読み込み(Import)</h3>
|
|
116
|
+
<pre><code><Import header /></code></pre>
|
|
117
|
+
|
|
118
|
+
<h3>Python 側から呼ぶ</h3>
|
|
119
|
+
<pre><code>from nexom.web.template import Templates
|
|
120
|
+
|
|
121
|
+
templates = Templates("./templates", "default", "document")
|
|
122
|
+
|
|
123
|
+
html = templates.default(title="Nexom")
|
|
124
|
+
return HtmlResponse(html)</code></pre>
|
|
125
|
+
|
|
126
|
+
<div class="note">
|
|
127
|
+
<strong>設計方針:</strong> テンプレートにロジックを入れず、「HTML と差し込み」に寄せることで、コード側をシンプルに保つ思想です。
|
|
128
|
+
</div>
|
|
129
|
+
</section>
|
|
130
|
+
|
|
131
|
+
<section id="cli">
|
|
132
|
+
<h2>CLI</h2>
|
|
133
|
+
|
|
134
|
+
<h3>動作確認</h3>
|
|
135
|
+
<pre><code>python -m nexom test</code></pre>
|
|
136
|
+
|
|
137
|
+
<h3>サーバープロジェクト生成</h3>
|
|
138
|
+
<pre><code>python -m nexom build-server myapp</code></pre>
|
|
139
|
+
|
|
140
|
+
<p>生成されるプロジェクトには以下が含まれます(構成はバージョンにより変わる場合があります)。</p>
|
|
141
|
+
<ul>
|
|
142
|
+
<li>最小のルーティング定義</li>
|
|
143
|
+
<li>テンプレート一式</li>
|
|
144
|
+
<li>静的ファイル用ディレクトリ</li>
|
|
145
|
+
<li>gunicorn / config の雛形</li>
|
|
146
|
+
</ul>
|
|
147
|
+
</section>
|
|
148
|
+
|
|
149
|
+
<section id="tips">
|
|
150
|
+
<h2>運用のコツ</h2>
|
|
151
|
+
|
|
152
|
+
<h3>「短く書く層」と「脱出口」を分ける</h3>
|
|
153
|
+
<ul>
|
|
154
|
+
<li>普段: <code>HtmlResponse</code> / <code>JsonResponse</code> を使う</li>
|
|
155
|
+
<li>詰めたい: <code>Response</code> で headers / content-type を直接制御</li>
|
|
156
|
+
</ul>
|
|
157
|
+
|
|
158
|
+
<h3>ルーティングは “薄く”</h3>
|
|
159
|
+
<p>handler 内で複雑化しそうなら、関数を分けて「処理の塊」を小さくするのが Nexom と相性良いです。</p>
|
|
160
|
+
|
|
161
|
+
<h3>テストのおすすめ</h3>
|
|
162
|
+
<p>公開運用を想定するなら、最低限この3つのテストを用意すると安心です。</p>
|
|
163
|
+
<ul>
|
|
164
|
+
<li>Path の引数抽出</li>
|
|
165
|
+
<li>Static の traversal ブロック</li>
|
|
166
|
+
<li>Response の Content-Type / charset の期待値</li>
|
|
167
|
+
</ul>
|
|
168
|
+
</section>
|
|
169
|
+
</Insert>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Callable, Iterable
|
|
4
|
+
|
|
5
|
+
from nexom.web.request import Request
|
|
6
|
+
from nexom.web.response import Response, ErrorResponse
|
|
7
|
+
from nexom.core.error import PathNotFoundError
|
|
8
|
+
|
|
9
|
+
# Project-local router
|
|
10
|
+
from router import routing
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def app(environ: dict, start_response: Callable) -> Iterable[bytes]:
|
|
14
|
+
"""
|
|
15
|
+
WSGI application entrypoint.
|
|
16
|
+
"""
|
|
17
|
+
try:
|
|
18
|
+
request = Request(environ)
|
|
19
|
+
path = request.path
|
|
20
|
+
|
|
21
|
+
p = routing.get(path)
|
|
22
|
+
res = p.call_handler(request)
|
|
23
|
+
|
|
24
|
+
except PathNotFoundError as e:
|
|
25
|
+
res = ErrorResponse(404, str(e))
|
|
26
|
+
except Exception as e:
|
|
27
|
+
res = ErrorResponse(500, str(e))
|
|
28
|
+
|
|
29
|
+
start_response(res.status_text, res.headers)
|
|
30
|
+
return [res.body]
|
|
File without changes
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from importlib import resources
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import shutil
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class ServerBuildOptions:
|
|
11
|
+
"""Options used to fill generated config.py."""
|
|
12
|
+
address: str = "0.0.0.0"
|
|
13
|
+
port: int = 8080
|
|
14
|
+
workers: int = 4
|
|
15
|
+
reload: bool = False
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _copy_from_package(pkg: str, filename: str, dest: Path) -> None:
|
|
19
|
+
"""
|
|
20
|
+
Copy a file from a package resource into the destination path.
|
|
21
|
+
"""
|
|
22
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
23
|
+
with resources.files(pkg).joinpath(filename).open("rb") as src, dest.open("wb") as dst:
|
|
24
|
+
shutil.copyfileobj(src, dst)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def server(work_dir: str | Path, name: str, *, options: ServerBuildOptions | None = None) -> Path:
|
|
28
|
+
"""
|
|
29
|
+
Generate a Nexom server project into `work_dir`.
|
|
30
|
+
|
|
31
|
+
This function copies template files bundled in the package (assets) and
|
|
32
|
+
writes a ready-to-run config.py.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
work_dir: Output directory where project files are created.
|
|
36
|
+
name: Project name (reserved for future use; currently not used).
|
|
37
|
+
options: Config defaults for generated config.py.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
The absolute path to the generated project directory.
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
FileExistsError: If target directories/files already exist.
|
|
44
|
+
ModuleNotFoundError / FileNotFoundError: If bundled assets are missing.
|
|
45
|
+
"""
|
|
46
|
+
_ = name # reserved (keep signature stable for future)
|
|
47
|
+
options = options or ServerBuildOptions()
|
|
48
|
+
|
|
49
|
+
out_dir = Path(work_dir).expanduser().resolve()
|
|
50
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
51
|
+
|
|
52
|
+
pages_dir = out_dir / "pages"
|
|
53
|
+
templates_dir = out_dir / "templates"
|
|
54
|
+
static_dir = out_dir / "static"
|
|
55
|
+
|
|
56
|
+
# Make sure we don't accidentally overwrite a project
|
|
57
|
+
for d in (pages_dir, templates_dir, static_dir):
|
|
58
|
+
if d.exists():
|
|
59
|
+
raise FileExistsError(f"Already exists: {d}")
|
|
60
|
+
|
|
61
|
+
pages_dir.mkdir()
|
|
62
|
+
templates_dir.mkdir()
|
|
63
|
+
static_dir.mkdir()
|
|
64
|
+
|
|
65
|
+
# ---- Copy pages ----
|
|
66
|
+
pages_pkg = "nexom.assets.server.pages"
|
|
67
|
+
for fn in ("__init__.py", "_templates.py", "default.py", "document.py"):
|
|
68
|
+
_copy_from_package(pages_pkg, fn, pages_dir / fn)
|
|
69
|
+
|
|
70
|
+
# ---- Copy templates ----
|
|
71
|
+
templates_pkg = "nexom.assets.server.templates"
|
|
72
|
+
for fn in ("base.html", "header.html", "footer.html", "default.html", "document.html"):
|
|
73
|
+
_copy_from_package(templates_pkg, fn, templates_dir / fn)
|
|
74
|
+
|
|
75
|
+
# ---- Copy static ----
|
|
76
|
+
static_pkg = "nexom.assets.server.static"
|
|
77
|
+
for fn in ("dog.jpeg", "style.css"):
|
|
78
|
+
_copy_from_package(static_pkg, fn, static_dir / fn)
|
|
79
|
+
|
|
80
|
+
# ---- Copy app files ----
|
|
81
|
+
app_pkg = "nexom.assets.server"
|
|
82
|
+
for fn in ("gunicorn.conf.py", "router.py", "wsgi.py", "config.py"):
|
|
83
|
+
_copy_from_package(app_pkg, fn, out_dir / fn)
|
|
84
|
+
|
|
85
|
+
# ---- Enable settings (format config.py) ----
|
|
86
|
+
config_path = out_dir / "config.py"
|
|
87
|
+
config_text = config_path.read_text(encoding="utf-8")
|
|
88
|
+
|
|
89
|
+
# NOTE: pwd_dir should be the generated project directory, not current cwd.
|
|
90
|
+
enabled = config_text.format(
|
|
91
|
+
pwd_dir=str(out_dir),
|
|
92
|
+
g_address=options.address,
|
|
93
|
+
g_port=options.port,
|
|
94
|
+
g_workers=options.workers,
|
|
95
|
+
g_reload=options.reload,
|
|
96
|
+
)
|
|
97
|
+
config_path.write_text(enabled, encoding="utf-8")
|
|
98
|
+
|
|
99
|
+
return out_dir
|
nexom/core/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from . import error
|