pentagon-framework 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ahmed Abdelkhaliq
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,46 @@
1
+ Metadata-Version: 2.4
2
+ Name: pentagon_framework
3
+ Version: 0.1.0
4
+ Summary: A web framework for Python.
5
+ Author-email: Ahmed Abdelkhaliq <abdelkhaliqqq@gmail.com>
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.8
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: watchdog
13
+ Dynamic: license-file
14
+
15
+
16
+ ![Logo](https://ik.imagekit.io/cin2tn3bj/pentagon_icon.png)
17
+ ## Pentagon - A Lightweight Python Web Framework
18
+
19
+ Pentagon is a modern, high-performance, web framework for building APIs with Python.
20
+ ## Features
21
+
22
+ - **Lighting Fast**: Very high performance.
23
+ - **Auto Documentation**: You register endpoints, Pentagon generates the documenations.
24
+ - **Easy**: Designed to be easy to use and learn, easy documentation.
25
+
26
+
27
+ ## Installation
28
+
29
+ Just run the pip command and you're all set!
30
+
31
+ ```bash
32
+ pip install pentagon
33
+ ```
34
+
35
+
36
+ ## Usage/Examples
37
+
38
+ Creating a project using pentagon
39
+
40
+ ```bash
41
+ penta-admin init project --name "My Cool Project"
42
+ ```
43
+ ## Authors
44
+
45
+ - [@abde1khaliq](https://www.github.com/abde1khaliq)
46
+
@@ -0,0 +1,32 @@
1
+
2
+ ![Logo](https://ik.imagekit.io/cin2tn3bj/pentagon_icon.png)
3
+ ## Pentagon - A Lightweight Python Web Framework
4
+
5
+ Pentagon is a modern, high-performance, web framework for building APIs with Python.
6
+ ## Features
7
+
8
+ - **Lighting Fast**: Very high performance.
9
+ - **Auto Documentation**: You register endpoints, Pentagon generates the documenations.
10
+ - **Easy**: Designed to be easy to use and learn, easy documentation.
11
+
12
+
13
+ ## Installation
14
+
15
+ Just run the pip command and you're all set!
16
+
17
+ ```bash
18
+ pip install pentagon
19
+ ```
20
+
21
+
22
+ ## Usage/Examples
23
+
24
+ Creating a project using pentagon
25
+
26
+ ```bash
27
+ penta-admin init project --name "My Cool Project"
28
+ ```
29
+ ## Authors
30
+
31
+ - [@abde1khaliq](https://www.github.com/abde1khaliq)
32
+
@@ -0,0 +1,5 @@
1
+ from .pentagon import Pentagon
2
+ from .response import Response
3
+ from .request import Request
4
+
5
+ __all__ = ["Pentagon", "Response", "Request"]
File without changes
@@ -0,0 +1,55 @@
1
+ import os
2
+ import argparse
3
+ from .templates import TEMPLATES
4
+
5
+ PROJECT_STRUCTURE = {
6
+ "": ["runserver.py", "README.md", "config.py"],
7
+ "app": ["__init__.py"],
8
+ "app/database": ["__init__.py", "connection.py"],
9
+ "app/models": ["__init__.py"],
10
+ "app/router": ["__init__.py", "index.py"],
11
+ "app/services": ["__init__.py"],
12
+ "app/schemas": ["__init__.py"],
13
+ "app/utils": ["__init__.py"]
14
+ }
15
+
16
+ def init_project(name, location):
17
+ project_path = os.path.join(location, name)
18
+ os.makedirs(project_path, exist_ok=True)
19
+
20
+ for rel_path, files in PROJECT_STRUCTURE.items():
21
+ folder_path = os.path.join(project_path, rel_path)
22
+ os.makedirs(folder_path, exist_ok=True)
23
+
24
+ for file in files:
25
+ full_file_path = os.path.join(folder_path, file)
26
+ # Use the full relative path for the template key if it exists, otherwise just the filename
27
+ template_key = os.path.join(rel_path, file) if rel_path else file
28
+
29
+ # Fallback to filename if specific path key not found
30
+ if template_key not in TEMPLATES:
31
+ template_key = file
32
+
33
+ with open(full_file_path, "w") as f:
34
+ template = TEMPLATES.get(template_key, f"# {file} generated by Pentagon CLI\n")
35
+ f.write(template.strip())
36
+
37
+ print(f"Project '{name}' created at {project_path}")
38
+
39
+ def main():
40
+ parser = argparse.ArgumentParser(prog="penta-admin", description="Pentagon Framework CLI")
41
+ subparsers = parser.add_subparsers(dest="command")
42
+
43
+ # Change 'create' back to 'init' to match user request and common practice
44
+ init_parser = subparsers.add_parser("init", help="Initialize a new project")
45
+ init_parser.add_argument("project", choices=["project"], help="Type of init (project)")
46
+ init_parser.add_argument("--name", required=True, help="Project name")
47
+ init_parser.add_argument("--location", default=".", help="Target directory")
48
+
49
+ args = parser.parse_args()
50
+
51
+ if args.command == "init" and args.project == "project":
52
+ init_project(args.name, args.location)
53
+
54
+ if __name__ == "__main__":
55
+ main()
@@ -0,0 +1,127 @@
1
+ TEMPLATES = {
2
+ 'runserver.py': """
3
+ import sys
4
+ import os
5
+ from pentagon import Pentagon
6
+ from config import setup_app
7
+
8
+ app = Pentagon(title="Pentagon Project", description="Project made using Pentagon for Python.")
9
+
10
+ if __name__ == "__main__":
11
+ # Fix for circular imports: ensures 'from runserver import app' works during discovery
12
+ sys.modules['runserver'] = sys.modules['__main__']
13
+
14
+ setup_app(app)
15
+ app.run(watch=True)
16
+ """,
17
+
18
+ 'config.py': """
19
+ import importlib
20
+ import os
21
+ import sys
22
+
23
+ def setup_app(app):
24
+ \"\"\"
25
+ Automatically links all routers and models to the Pentagon app.
26
+ \"\"\"
27
+ components = ['router', 'models']
28
+
29
+ # Ensure current directory is in path so 'app' package is found
30
+ if os.getcwd() not in sys.path:
31
+ sys.path.append(os.getcwd())
32
+
33
+ base_path = os.path.join(os.path.dirname(__file__), 'app')
34
+
35
+ for component in components:
36
+ comp_path = os.path.join(base_path, component)
37
+ if not os.path.exists(comp_path):
38
+ continue
39
+
40
+ for file in os.listdir(comp_path):
41
+ if file.endswith('.py') and file != '__init__.py':
42
+ module_name = f"app.{component}.{file[:-3]}"
43
+ try:
44
+ # This will trigger the decorators (e.g., @app.get) in the module
45
+ importlib.import_module(module_name)
46
+ print(f"Loaded {component}: {module_name}")
47
+ except Exception as e:
48
+ print(f"Failed to load {module_name}: {e}")
49
+
50
+ print(f"Pentagon: {len(app.router)} endpoints registered.")
51
+ """,
52
+
53
+ "README.md": """
54
+ # Pentagon Project
55
+
56
+ This is a project created using the Pentagon framework.
57
+
58
+ ## Structure
59
+ - `/app`: Contains your application logic.
60
+ - `/database`: DB connection and session management.
61
+ - `/models`: SQLAlchemy models.
62
+ - `/router`: API endpoints.
63
+ - `/services`: Business logic.
64
+ - `/schemas`: Data validation schemas.
65
+ - `/utils`: Helper functions.
66
+ - `runserver.py`: Entry point for the application.
67
+ - `config.py`: Application configuration and component linker.
68
+
69
+ ## How to use
70
+ 1. Define your models in `app/models/`.
71
+ 2. Define your routes in `app/router/`.
72
+ 3. Run `python runserver.py` and everything will be auto-linked!
73
+ """,
74
+
75
+ "connection.py": """
76
+ from sqlalchemy import Column, Integer, String, create_engine
77
+ from sqlalchemy.ext.declarative import declarative_base
78
+ from sqlalchemy.orm import sessionmaker
79
+ import os
80
+
81
+ DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./pentagon.db")
82
+
83
+ engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False} if "sqlite" in DATABASE_URL else {})
84
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
85
+
86
+ Base = declarative_base()
87
+
88
+ def get_db():
89
+ db = SessionLocal()
90
+ try:
91
+ yield db
92
+ finally:
93
+ db.close()
94
+
95
+ def init_db():
96
+ Base.metadata.create_all(bind=engine)
97
+ """,
98
+
99
+ "app/__init__.py": """
100
+ # Application package root
101
+ """,
102
+
103
+ "database/__init__.py": """
104
+ from .connection import engine, Base, SessionLocal, get_db, init_db
105
+ """,
106
+
107
+ "models/__init__.py": """
108
+ # Import all models here or let config.py auto-load them from this directory
109
+ """,
110
+
111
+ "router/__init__.py": """
112
+ # Import all routers here or let config.py auto-load them from this directory
113
+ """,
114
+
115
+ "index.py": """
116
+ from runserver import app
117
+ from pentagon.utils import render_welcome
118
+
119
+ @app.get("/", group="General", description="Home page")
120
+ def index(request):
121
+ return render_welcome(app.title, app.description)
122
+ """,
123
+
124
+ "services/__init__.py": "# Business logic package",
125
+ "schemas/__init__.py": "# Validation schemas package",
126
+ "utils/__init__.py": "# Utility functions package"
127
+ }
@@ -0,0 +1,260 @@
1
+ from .response import Response
2
+
3
+ def render_docs(title, description, docs):
4
+ grouped_docs = {}
5
+ for path, meta in docs.items():
6
+ group = meta.get("group") or "General"
7
+ if group not in grouped_docs:
8
+ grouped_docs[group] = []
9
+ grouped_docs[group].append((path, meta))
10
+
11
+ sections = []
12
+ for group, items in grouped_docs.items():
13
+ rows = "".join(
14
+ f"""
15
+ <tr class="table-row">
16
+ <td class="code-cell"><code>{path}</code></td>
17
+ <td class="methods-cell">
18
+ <div class="methods-wrapper">
19
+ {', '.join(meta['methods'])}
20
+ </div>
21
+ </td>
22
+ <td class="desc-cell">{meta['description']}</td>
23
+ </tr>
24
+ """
25
+ for path, meta in items
26
+ )
27
+
28
+ sections.append(f"""
29
+ <div class="group-section">
30
+ <h2 class="group-header">{group}</h2>
31
+ <div class="table-wrapper">
32
+ <table>
33
+ <thead>
34
+ <tr>
35
+ <th>Path</th>
36
+ <th>Methods</th>
37
+ <th>Description</th>
38
+ </tr>
39
+ </thead>
40
+ <tbody>
41
+ {rows}
42
+ </tbody>
43
+ </table>
44
+ </div>
45
+ </div>
46
+ """)
47
+
48
+ all_sections = "\n".join(sections)
49
+
50
+ html = f"""
51
+ <!DOCTYPE html>
52
+ <html lang="en">
53
+ <head>
54
+ <meta charset="UTF-8">
55
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
56
+ <title>{title} Documentation</title>
57
+
58
+ <!-- Import Syne Font -->
59
+ <link rel="preconnect" href="https://fonts.googleapis.com">
60
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
61
+
62
+ <style>
63
+ /* Sleek Monochrome Theme Variables */
64
+ :root {{
65
+ --bg: #ffffff;
66
+ --text: #000000;
67
+ --border: rgba(0, 0, 0, 0.08);
68
+ --hover-bg: rgba(0, 0, 0, 0.02);
69
+ --code-bg: rgba(0, 0, 0, 0.04);
70
+ --invert-bg: #000000;
71
+ --invert-text: #ffffff;
72
+ }}
73
+
74
+ [data-theme="dark"] {{
75
+ --bg: #050505;
76
+ --text: #ffffff;
77
+ --border: rgba(255, 255, 255, 0.1);
78
+ --hover-bg: rgba(255, 255, 255, 0.03);
79
+ --code-bg: rgba(255, 255, 255, 0.06);
80
+ --invert-bg: #ffffff;
81
+ --invert-text: #000000;
82
+ }}
83
+
84
+ /* Reset & Typography */
85
+ * {{ box-sizing: border-box; margin: 0; padding: 0; }}
86
+ body {{
87
+ font-family: sans-serif;
88
+ background-color: var(--bg);
89
+ color: var(--text);
90
+ transition: background-color 0.3s ease, color 0.3s ease;
91
+ line-height: 1.6;
92
+ -webkit-font-smoothing: antialiased;
93
+ -moz-osx-font-smoothing: grayscale;
94
+ }}
95
+
96
+ .container {{
97
+ max-width: 1000px;
98
+ margin: 0 auto;
99
+ padding: 80px 24px;
100
+ }}
101
+
102
+ /* Header */
103
+ header {{
104
+ display: flex;
105
+ justify-content: space-between;
106
+ align-items: center;
107
+ margin-bottom: 60px;
108
+ }}
109
+ .header-content h1 {{
110
+ font-size: 42px;
111
+ font-weight: 800;
112
+ letter-spacing: -0.02em;
113
+ margin-bottom: 8px;
114
+ }}
115
+ .header-content p {{
116
+ font-size: 18px;
117
+ font-weight: 500;
118
+ opacity: 0.6;
119
+ }}
120
+
121
+ /* Pill-shaped Theme Toggle */
122
+ button#theme-toggle {{
123
+ background: var(--invert-bg);
124
+ color: var(--invert-text);
125
+ border: none;
126
+ padding: 12px 24px;
127
+ font-family: 'Syne', sans-serif;
128
+ font-size: 14px;
129
+ font-weight: 700;
130
+ border-radius: 999px; /* Pill shape */
131
+ cursor: pointer;
132
+ transition: transform 0.2s ease, opacity 0.2s ease;
133
+ }}
134
+
135
+ .group-section {{
136
+ margin-bottom: 64px;
137
+ }}
138
+
139
+ /* Table Wrapper (Rounded Corners) */
140
+ h2.group-header {{
141
+ font-size: 24px;
142
+ font-weight: 700;
143
+ margin-bottom: 24px;
144
+ letter-spacing: -0.01em;
145
+ text-transform: capitalize;
146
+ }}
147
+
148
+ .table-wrapper {{
149
+ background-color: var(--bg);
150
+ border: 1px solid var(--border);
151
+ border-radius: 5px;
152
+ overflow: hidden;
153
+ }}
154
+
155
+ table {{
156
+ width: 100%;
157
+ border-collapse: collapse;
158
+ text-align: left;
159
+ }}
160
+
161
+ th, td {{
162
+ padding: 20px 24px;
163
+ border-bottom: 1px solid var(--border);
164
+ }}
165
+
166
+ /* Remove bottom border from last row */
167
+ tr:last-child td {{
168
+ border-bottom: none;
169
+ }}
170
+
171
+ th {{
172
+ font-weight: 600;
173
+ font-size: 14px;
174
+ opacity: 0.5;
175
+ text-transform: uppercase;
176
+ letter-spacing: 0.05em;
177
+ background-color: var(--hover-bg);
178
+ }}
179
+
180
+ .table-row {{
181
+ transition: background-color 0.2s ease;
182
+ }}
183
+ .table-row:hover {{
184
+ background-color: var(--hover-bg);
185
+ }}
186
+
187
+ /* Sleek Code Tags */
188
+ code {{
189
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
190
+ background-color: var(--code-bg);
191
+ padding: 6px 12px;
192
+ font-size: 14px;
193
+ border-radius: 8px; /* Rounded code blocks */
194
+ font-weight: 500;
195
+ }}
196
+
197
+ .methods-wrapper {{
198
+ display: inline-block;
199
+ border: 1px solid var(--border);
200
+ padding: 6px 16px;
201
+ border-radius: 999px; /* Pill-shaped methods */
202
+ font-size: 13px;
203
+ font-weight: 700;
204
+ }}
205
+
206
+ .code-cell {{ width: 35%; }}
207
+ .methods-cell {{ width: 25%; }}
208
+ .desc-cell {{ font-weight: 500; opacity: 0.8; }}
209
+
210
+ /* Mobile Responsiveness */
211
+ @media (max-width: 768px) {{
212
+ header {{
213
+ flex-direction: column;
214
+ align-items: flex-start;
215
+ gap: 24px;
216
+ }}
217
+ .container {{ padding: 40px 20px; }}
218
+ th, td {{ padding: 16px; }}
219
+ .code-cell, .methods-cell {{ width: auto; }}
220
+ }}
221
+ </style>
222
+ </head>
223
+ <body>
224
+ <div class="container">
225
+ <header>
226
+ <div class="header-content">
227
+ <h1>{title}</h1>
228
+ <p>{description}</p>
229
+ </div>
230
+ <button id="theme-toggle">Toggle Theme</button>
231
+ </header>
232
+
233
+ <main>
234
+ {all_sections}
235
+ </main>
236
+ </div>
237
+
238
+ <script>
239
+ const toggle = document.getElementById('theme-toggle');
240
+
241
+ // Check for user's saved preference or OS level preference
242
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
243
+ const savedTheme = localStorage.getItem('pentagon-theme');
244
+
245
+ if (savedTheme === 'dark' || (!savedTheme && prefersDark)) {{
246
+ document.documentElement.setAttribute('data-theme', 'dark');
247
+ }}
248
+
249
+ // Handle toggle click
250
+ toggle.addEventListener('click', () => {{
251
+ const currentTheme = document.documentElement.getAttribute('data-theme');
252
+ const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
253
+ document.documentElement.setAttribute('data-theme', newTheme);
254
+ localStorage.setItem('pentagon-theme', newTheme);
255
+ }});
256
+ </script>
257
+ </body>
258
+ </html>
259
+ """
260
+ return Response(html, content_type="text/html")
@@ -0,0 +1,73 @@
1
+ from wsgiref.simple_server import make_server
2
+ from .request import Request
3
+ from .response import Response
4
+ from .docs import render_docs
5
+ from .worker import ReloadHandler
6
+ from watchdog.observers import Observer
7
+
8
+ class Pentagon:
9
+ def __init__(self, title="Pentagon Framework", description="A web framework for Python."):
10
+ self.title = title
11
+ self.description = description
12
+ self.router = {}
13
+ self.docs = {}
14
+
15
+ self.router["/docs"] = self.docs_html
16
+ self.docs["/docs"] = {
17
+ "group": "General",
18
+ "methods": ["GET"],
19
+ "description": "Browsable API documentation"
20
+ }
21
+
22
+ def docs_html(self, request: Request):
23
+ return render_docs(self.title, self.description, self.docs)
24
+
25
+
26
+ def __call__(self, environ, start_response):
27
+ request = Request(environ)
28
+ handler = self.router.get(request.path, self.not_found)
29
+ response = handler(request)
30
+ start_response(response.status, response.headers)
31
+ return response.body
32
+
33
+ def not_found(self, request: Request):
34
+ return Response("Not Found", status="404 Not Found")
35
+
36
+ def route(self, path: str, group: str=None, methods: list=["GET"], description: str=""):
37
+ def decorator(func):
38
+ self.router[path] = func
39
+ self.docs[path] = {
40
+ "group": group,
41
+ "methods": methods,
42
+ "description": description
43
+ }
44
+ return func
45
+ return decorator
46
+
47
+ def get(self, path: str, **kwargs):
48
+ return self.route(path, methods=["GET"], **kwargs)
49
+
50
+ def post(self, path: str, **kwargs):
51
+ return self.route(path, methods=["POST"], **kwargs)
52
+
53
+ def put(self, path: str, **kwargs):
54
+ return self.route(path, methods=["PUT"], **kwargs)
55
+
56
+ def delete(self, path: str, **kwargs):
57
+ return self.route(path, methods=["DELETE"], **kwargs)
58
+
59
+ def run(self, host="127.0.0.1", port=8000, watch=False):
60
+ if watch:
61
+ observer = Observer()
62
+ observer.schedule(ReloadHandler(), path=".", recursive=True)
63
+ observer.start()
64
+ print("File watcher active. Auto-reload on changes.")
65
+
66
+ with make_server(host, port, self) as httpd:
67
+ print(f"Serving on http://{host}:{port}")
68
+ try:
69
+ httpd.serve_forever()
70
+ except KeyboardInterrupt:
71
+ if watch:
72
+ observer.stop()
73
+ observer.join()
@@ -0,0 +1,24 @@
1
+ class Request:
2
+ def __init__(self, environ):
3
+ self.method = environ['REQUEST_METHOD']
4
+ self.path = environ['PATH_INFO']
5
+ self.query_string = environ.get('QUERY_STRING', '')
6
+ self.headers = self.parse_headers(environ)
7
+ self.body = self.read_body(environ)
8
+
9
+ def parse_headers(self, environ):
10
+ headers = {}
11
+ for key, value in environ.items():
12
+ if key.startswith('HTTP_'):
13
+ header_name = key[5:].replace('_', '-').title()
14
+ headers[header_name] = value
15
+ return headers
16
+
17
+ def read_body(self, environ):
18
+ try:
19
+ content_length = int(environ.get('CONTENT_LENGTH', 0))
20
+ except (ValueError, TypeError):
21
+ content_length = 0
22
+ if content_length > 0:
23
+ return environ['wsgi.input'].read(content_length)
24
+ return b''
@@ -0,0 +1,16 @@
1
+ import json
2
+
3
+ class Response:
4
+ def __init__(self, body, status="200 OK", content_type="text/plain"):
5
+ if isinstance(body, dict) or isinstance(body, list):
6
+ body = json.dumps(body)
7
+ content_type = content_type or "application/json"
8
+ elif isinstance(body, bytes):
9
+ content_type = content_type or "application/octet-stream"
10
+ elif isinstance(body, str):
11
+ content_type = content_type or "text/plain"
12
+ self.status = status
13
+ self.headers = [("Content-Type", content_type)]
14
+ if isinstance(body, str):
15
+ body = body.encode("utf-8")
16
+ self.body = [body]
@@ -0,0 +1,143 @@
1
+ from .response import Response
2
+
3
+ def render_welcome(title, description):
4
+ html = f"""
5
+ <!DOCTYPE html>
6
+ <html lang="en">
7
+ <head>
8
+ <meta charset="UTF-8">
9
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
10
+ <title>Welcome to {title}</title>
11
+
12
+ <!-- Import Syne Font -->
13
+ <link rel="preconnect" href="https://fonts.googleapis.com">
14
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
15
+
16
+ <style>
17
+ :root {{
18
+ --bg: #ffffff;
19
+ --text: #000000;
20
+ --border: rgba(0, 0, 0, 0.08);
21
+ --hover-bg: rgba(0, 0, 0, 0.02);
22
+ --invert-bg: #000000;
23
+ --invert-text: #ffffff;
24
+ }}
25
+
26
+ [data-theme="dark"] {{
27
+ --bg: #050505;
28
+ --text: #ffffff;
29
+ --border: rgba(255, 255, 255, 0.1);
30
+ --hover-bg: rgba(255, 255, 255, 0.03);
31
+ --invert-bg: #ffffff;
32
+ --invert-text: #000000;
33
+ }}
34
+
35
+ * {{ box-sizing: border-box; margin: 0; padding: 0; }}
36
+ body {{
37
+ font-family: sans-serif;
38
+ background-color: var(--bg);
39
+ color: var(--text);
40
+ transition: background-color 0.3s ease, color 0.3s ease;
41
+ display: flex;
42
+ align-items: center;
43
+ justify-content: center;
44
+ min-height: 100vh;
45
+ -webkit-font-smoothing: antialiased;
46
+ }}
47
+
48
+ .content {{
49
+ text-align: center;
50
+ max-width: 600px;
51
+ padding: 40px;
52
+ }}
53
+
54
+ h1 {{
55
+ font-size: 64px;
56
+ font-weight: 800;
57
+ letter-spacing: -0.04em;
58
+ margin-bottom: 16px;
59
+ }}
60
+
61
+ p {{
62
+ font-size: 20px;
63
+ opacity: 0.6;
64
+ margin-bottom: 48px;
65
+ line-height: 1.5;
66
+ }}
67
+
68
+ .actions {{
69
+ display: flex;
70
+ gap: 16px;
71
+ justify-content: center;
72
+ }}
73
+
74
+ .btn {{
75
+ text-decoration: none;
76
+ padding: 16px 32px;
77
+ border-radius: 999px;
78
+ font-weight: 700;
79
+ font-size: 16px;
80
+ transition: transform 0.2s ease, opacity 0.2s;
81
+ }}
82
+
83
+ .btn-primary {{
84
+ background-color: var(--invert-bg);
85
+ color: var(--invert-text);
86
+ }}
87
+
88
+ .btn-secondary {{
89
+ border: 1px solid var(--border);
90
+ color: var(--text);
91
+ }}
92
+
93
+ .btn:hover {{
94
+ transform: translateY(-2px);
95
+ opacity: 0.9;
96
+ }}
97
+
98
+ #theme-toggle {{
99
+ position: fixed;
100
+ top: 24px;
101
+ right: 24px;
102
+ background: none;
103
+ border: 1px solid var(--border);
104
+ color: var(--text);
105
+ padding: 8px 16px;
106
+ border-radius: 999px;
107
+ cursor: pointer;
108
+ font-weight: 600;
109
+ font-size: 12px;
110
+ }}
111
+ </style>
112
+ </head>
113
+ <body>
114
+ <button id="theme-toggle">Toggle Theme</button>
115
+ <div class="content">
116
+ <h1>{title}</h1>
117
+ <p>{description}</p>
118
+ <div class="actions">
119
+ <a href="/docs" class="btn btn-primary">View Documentation</a>
120
+ <a href="https://github.com" class="btn btn-secondary">GitHub</a>
121
+ </div>
122
+ </div>
123
+
124
+ <script>
125
+ const toggle = document.getElementById('theme-toggle');
126
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
127
+ const savedTheme = localStorage.getItem('pentagon-theme');
128
+
129
+ if (savedTheme === 'dark' || (!savedTheme && prefersDark)) {{
130
+ document.documentElement.setAttribute('data-theme', 'dark');
131
+ }}
132
+
133
+ toggle.addEventListener('click', () => {{
134
+ const currentTheme = document.documentElement.getAttribute('data-theme');
135
+ const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
136
+ document.documentElement.setAttribute('data-theme', newTheme);
137
+ localStorage.setItem('pentagon-theme', newTheme);
138
+ }});
139
+ </script>
140
+ </body>
141
+ </html>
142
+ """
143
+ return Response(html, content_type="text/html")
@@ -0,0 +1,11 @@
1
+ from watchdog.events import FileSystemEventHandler
2
+ import os, sys
3
+
4
+ class ReloadHandler(FileSystemEventHandler):
5
+ def on_modified(self, event):
6
+ if event.is_directory:
7
+ return
8
+ if event.src_path.endswith(".py"):
9
+ print(f"Reloading due to change in: {os.path.basename(event.src_path)}")
10
+ # Clear __pycache__ or other potential issues? Not really needed for execv
11
+ os.execv(sys.executable, [sys.executable] + sys.argv)
@@ -0,0 +1,46 @@
1
+ Metadata-Version: 2.4
2
+ Name: pentagon_framework
3
+ Version: 0.1.0
4
+ Summary: A web framework for Python.
5
+ Author-email: Ahmed Abdelkhaliq <abdelkhaliqqq@gmail.com>
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.8
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: watchdog
13
+ Dynamic: license-file
14
+
15
+
16
+ ![Logo](https://ik.imagekit.io/cin2tn3bj/pentagon_icon.png)
17
+ ## Pentagon - A Lightweight Python Web Framework
18
+
19
+ Pentagon is a modern, high-performance, web framework for building APIs with Python.
20
+ ## Features
21
+
22
+ - **Lighting Fast**: Very high performance.
23
+ - **Auto Documentation**: You register endpoints, Pentagon generates the documenations.
24
+ - **Easy**: Designed to be easy to use and learn, easy documentation.
25
+
26
+
27
+ ## Installation
28
+
29
+ Just run the pip command and you're all set!
30
+
31
+ ```bash
32
+ pip install pentagon
33
+ ```
34
+
35
+
36
+ ## Usage/Examples
37
+
38
+ Creating a project using pentagon
39
+
40
+ ```bash
41
+ penta-admin init project --name "My Cool Project"
42
+ ```
43
+ ## Authors
44
+
45
+ - [@abde1khaliq](https://www.github.com/abde1khaliq)
46
+
@@ -0,0 +1,19 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ pentagon/__init__.py
5
+ pentagon/docs.py
6
+ pentagon/pentagon.py
7
+ pentagon/request.py
8
+ pentagon/response.py
9
+ pentagon/utils.py
10
+ pentagon/worker.py
11
+ pentagon/cli/__init__.py
12
+ pentagon/cli/main.py
13
+ pentagon/cli/templates.py
14
+ pentagon_framework.egg-info/PKG-INFO
15
+ pentagon_framework.egg-info/SOURCES.txt
16
+ pentagon_framework.egg-info/dependency_links.txt
17
+ pentagon_framework.egg-info/entry_points.txt
18
+ pentagon_framework.egg-info/requires.txt
19
+ pentagon_framework.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ penta-admin = pentagon.cli.main:main
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pentagon_framework"
7
+ version = "0.1.0"
8
+ authors = [
9
+ { name="Ahmed Abdelkhaliq", email="abdelkhaliqqq@gmail.com" },
10
+ ]
11
+ description = "A web framework for Python."
12
+ readme = "README.md"
13
+ requires-python = ">=3.8"
14
+ dependencies = [
15
+ "watchdog",
16
+ ]
17
+ classifiers = [
18
+ "Programming Language :: Python :: 3",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Operating System :: OS Independent",
21
+ ]
22
+
23
+ [project.scripts]
24
+ penta-admin = "pentagon.cli.main:main"
25
+
26
+ [tool.setuptools.packages.find]
27
+ where = ["."]
28
+ include = ["pentagon*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+