SlimeWeb 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,5 @@
1
+ .env/
2
+ web/.*
3
+ web/target/*
4
+ slime.egg-info/
5
+ *.pyc
@@ -0,0 +1,154 @@
1
+ Metadata-Version: 2.4
2
+ Name: SlimeWeb
3
+ Version: 0.1.0
4
+ Summary: A Simple fast lightweight webframework lib for python
5
+ Author-email: "S.ABILASH" <abinix01@gmail.com>
6
+ Requires-Python: >=3.12
7
+ Description-Content-Type: text/markdown
8
+
9
+ # Slime – A Rust + Python Hybrid Web Framework
10
+
11
+ Slime is a high-performance web framework that combines Rust and python
12
+
13
+ It is designed for developers who want Python developer experience with a Rust-powered server core.
14
+
15
+
16
+ ## Installation
17
+
18
+ - pip install slimeweb
19
+ - slime new <Project Name>
20
+ - cd <Project Name>
21
+ - slime run <Project Name>
22
+
23
+
24
+
25
+ ---
26
+
27
+ ## Features
28
+
29
+ - Python handler functions
30
+ - Multiple worker pool model
31
+ - Synchronous handler
32
+ - Multipart form support
33
+ - File uploads
34
+ - Streaming Response
35
+ - Cookie signing
36
+ - Custom headers
37
+ - JSON / HTML / raw responses
38
+ - Templates rendering with context
39
+ - Hot reload of templates in dev mode
40
+ - WebSocket
41
+
42
+ ---
43
+
44
+
45
+
46
+ ## Basic Application
47
+
48
+ ```python
49
+ @app.route(path="/", method="GET")
50
+ def index(req, resp):
51
+ return resp.plain("Hello from Slime")
52
+ ```
53
+
54
+
55
+ ## Request Body
56
+
57
+ ```python
58
+ @app.route(path="/test", method="POST")
59
+ def hello(req, resp):
60
+ print("query", req.query)
61
+ print("params", req.params)
62
+ print("body", req.body)
63
+ print("json", req.json)
64
+ print("form", req.form)
65
+ print("text", req.text)
66
+ print("bytes", req.bytes)
67
+ print("file",req.file)
68
+ return resp.json({"status": "ok"})
69
+ ```
70
+
71
+
72
+
73
+ ### File Upload
74
+ ```python
75
+ @app.route(path="/test", method="POST")
76
+ def hello(req, resp):
77
+ file = req.file[0] # use can upload multiple files
78
+ print(file.filename)
79
+ print(file.content_type)
80
+ print(file.file_path)
81
+ print(file.file_size)
82
+ print(file.extension)
83
+ file.save(f"testing_file.{file.extension}")
84
+ return resp.json({"status": "ok"})
85
+
86
+ ```
87
+
88
+
89
+ ### Template Render
90
+
91
+ ```python
92
+ @app.route(path="/", method="GET")
93
+ def land(req, resp):
94
+ html = req.render("hello.html", **{"name": "abilash", "slimeVersion": "0.0.1"})
95
+ return resp.html(html)
96
+
97
+ ```
98
+
99
+ ### Middleware
100
+
101
+ ```python
102
+ #NOTE: middleware should be declared after declaring route handler
103
+ # LifeCycle handler =>
104
+ # middle before request -> router handler -> middle after request
105
+
106
+ @app.middle_after(path="/", method="GET")
107
+ def land_after(req, resp):
108
+ resp.set_header("BEFORE", "Request")
109
+
110
+
111
+ @app.middle_before(path="/", method="GET")
112
+ def land_before(req, resp):
113
+ resp.set_header("AFTER", "REQUEST")
114
+
115
+ ```
116
+
117
+ ### Streaming
118
+
119
+ ```python
120
+ @app.route(path="/stream", method="GET", stream="text/plain")
121
+ def stream_me(req, resp):
122
+ resp.start_stream()
123
+ for i in range(5):
124
+ resp.send(i)
125
+ resp.close()
126
+
127
+ # OR u can use @app.stream
128
+
129
+ @app.stream(path="/stream", method="GET", content="text/plain")
130
+ def stream_me(req, resp):
131
+ resp.start_stream()
132
+ for i in range(5):
133
+ resp.send(i)
134
+ resp.close()
135
+ ```
136
+
137
+
138
+
139
+ ### WebSocket
140
+ ```python
141
+ @app.websocket(path="/chat", method="GET")
142
+ def chatty(req, resp):
143
+
144
+ def read_me(msg):
145
+ if not resp.is_closed():
146
+ resp.send(msg)
147
+
148
+ resp.on_message(read_me)
149
+
150
+ def close_me():
151
+ pass
152
+
153
+ resp.on_close(close_me)
154
+ ```
@@ -0,0 +1,146 @@
1
+ # Slime – A Rust + Python Hybrid Web Framework
2
+
3
+ Slime is a high-performance web framework that combines Rust and python
4
+
5
+ It is designed for developers who want Python developer experience with a Rust-powered server core.
6
+
7
+
8
+ ## Installation
9
+
10
+ - pip install slimeweb
11
+ - slime new <Project Name>
12
+ - cd <Project Name>
13
+ - slime run <Project Name>
14
+
15
+
16
+
17
+ ---
18
+
19
+ ## Features
20
+
21
+ - Python handler functions
22
+ - Multiple worker pool model
23
+ - Synchronous handler
24
+ - Multipart form support
25
+ - File uploads
26
+ - Streaming Response
27
+ - Cookie signing
28
+ - Custom headers
29
+ - JSON / HTML / raw responses
30
+ - Templates rendering with context
31
+ - Hot reload of templates in dev mode
32
+ - WebSocket
33
+
34
+ ---
35
+
36
+
37
+
38
+ ## Basic Application
39
+
40
+ ```python
41
+ @app.route(path="/", method="GET")
42
+ def index(req, resp):
43
+ return resp.plain("Hello from Slime")
44
+ ```
45
+
46
+
47
+ ## Request Body
48
+
49
+ ```python
50
+ @app.route(path="/test", method="POST")
51
+ def hello(req, resp):
52
+ print("query", req.query)
53
+ print("params", req.params)
54
+ print("body", req.body)
55
+ print("json", req.json)
56
+ print("form", req.form)
57
+ print("text", req.text)
58
+ print("bytes", req.bytes)
59
+ print("file",req.file)
60
+ return resp.json({"status": "ok"})
61
+ ```
62
+
63
+
64
+
65
+ ### File Upload
66
+ ```python
67
+ @app.route(path="/test", method="POST")
68
+ def hello(req, resp):
69
+ file = req.file[0] # use can upload multiple files
70
+ print(file.filename)
71
+ print(file.content_type)
72
+ print(file.file_path)
73
+ print(file.file_size)
74
+ print(file.extension)
75
+ file.save(f"testing_file.{file.extension}")
76
+ return resp.json({"status": "ok"})
77
+
78
+ ```
79
+
80
+
81
+ ### Template Render
82
+
83
+ ```python
84
+ @app.route(path="/", method="GET")
85
+ def land(req, resp):
86
+ html = req.render("hello.html", **{"name": "abilash", "slimeVersion": "0.0.1"})
87
+ return resp.html(html)
88
+
89
+ ```
90
+
91
+ ### Middleware
92
+
93
+ ```python
94
+ #NOTE: middleware should be declared after declaring route handler
95
+ # LifeCycle handler =>
96
+ # middle before request -> router handler -> middle after request
97
+
98
+ @app.middle_after(path="/", method="GET")
99
+ def land_after(req, resp):
100
+ resp.set_header("BEFORE", "Request")
101
+
102
+
103
+ @app.middle_before(path="/", method="GET")
104
+ def land_before(req, resp):
105
+ resp.set_header("AFTER", "REQUEST")
106
+
107
+ ```
108
+
109
+ ### Streaming
110
+
111
+ ```python
112
+ @app.route(path="/stream", method="GET", stream="text/plain")
113
+ def stream_me(req, resp):
114
+ resp.start_stream()
115
+ for i in range(5):
116
+ resp.send(i)
117
+ resp.close()
118
+
119
+ # OR u can use @app.stream
120
+
121
+ @app.stream(path="/stream", method="GET", content="text/plain")
122
+ def stream_me(req, resp):
123
+ resp.start_stream()
124
+ for i in range(5):
125
+ resp.send(i)
126
+ resp.close()
127
+ ```
128
+
129
+
130
+
131
+ ### WebSocket
132
+ ```python
133
+ @app.websocket(path="/chat", method="GET")
134
+ def chatty(req, resp):
135
+
136
+ def read_me(msg):
137
+ if not resp.is_closed():
138
+ resp.send(msg)
139
+
140
+ resp.on_message(read_me)
141
+
142
+ def close_me():
143
+ pass
144
+
145
+ resp.on_close(close_me)
146
+ ```
@@ -0,0 +1,22 @@
1
+ [project]
2
+ name = "SlimeWeb"
3
+ version = "0.1.0"
4
+ description = "A Simple fast lightweight webframework lib for python"
5
+ authors = [{ name= "S.ABILASH", email="abinix01@gmail.com" }]
6
+ readme = "README.md"
7
+ requires-python = ">=3.12"
8
+
9
+ [project.scripts]
10
+ slime = "slimeweb.cli:main"
11
+
12
+ [build-system]
13
+ requires = ["hatchling"]
14
+ build-backend = "hatchling.build"
15
+
16
+
17
+ [tool.hatch.build]
18
+ packages = ["slimeweb"]
19
+ artifacts = [
20
+ "slimeweb/**/*.so",
21
+ "slimeweb/**/*.pyd"
22
+ ]
@@ -0,0 +1 @@
1
+ from .slime import Slime
@@ -0,0 +1,124 @@
1
+ # AUTHOR: S.ABILASH
2
+ # Email: abinix01@gmail.com
3
+
4
+ import os
5
+ import shutil
6
+ import subprocess as sp
7
+ import sys
8
+ from pathlib import Path
9
+
10
+
11
+ def create_project(name: str):
12
+ if shutil.which("uv") is None:
13
+ print("\n[*] uv is not found installing...")
14
+ command = [sys.executable, "-m", "pip", "install", "uv"]
15
+ sp.run(command, check=True)
16
+
17
+ print(f"\n[*] Creating project {name}")
18
+ root = Path.cwd() / name
19
+
20
+ static_path = root / "static"
21
+ template_path = root / "templates"
22
+
23
+ static_path.mkdir(parents=True, exist_ok=True)
24
+ template_path.mkdir(parents=True, exist_ok=True)
25
+
26
+ script_path = root / "main.py"
27
+
28
+ code = """
29
+ from slimeweb import Slime
30
+
31
+ app = Slime(__file__)
32
+
33
+ @app.route(path="/", method="GET")
34
+ def home(req, resp):
35
+ return resp.plain("Hello World from slime")
36
+
37
+ if __name__ == "__main__":
38
+ app.serve(dev=True)
39
+ """
40
+
41
+ script_path.write_text(code)
42
+
43
+ print("[*] Creating an env")
44
+ if not (root / ".venv").exists():
45
+ os.chdir(root)
46
+ sp.run(
47
+ ["uv", "venv", "--python", "python3.14t"],
48
+ check=True,
49
+ stdout=sp.DEVNULL,
50
+ stderr=sp.DEVNULL,
51
+ )
52
+ sp.run(
53
+ ["uv", "python", "pin", "python3.14t"],
54
+ check=True,
55
+ stdout=sp.DEVNULL,
56
+ stderr=sp.DEVNULL,
57
+ )
58
+ sp.run(["uv", "init"])
59
+ sp.run(["uv", "add", "slimeweb"], check=True)
60
+ print(f"\n\n[*] Project '{name}' created 🎉🎉🎉")
61
+ print(f"[*] cd {name} ")
62
+
63
+ print("[*] slime run main")
64
+
65
+
66
+ def run_project(script: Path):
67
+
68
+ script_path = Path.cwd()
69
+ if script.suffix == ".py":
70
+ script_path = script_path.joinpath(Path(script))
71
+ else:
72
+ script_path = script_path.joinpath(Path(script).with_suffix(".py"))
73
+
74
+ if not script_path.exists():
75
+ print(f"❌ Script '{script_path}' not found")
76
+ sys.exit(1)
77
+
78
+ sp.run(["uv", "run", "python", "-Xgil=0", script_path])
79
+
80
+
81
+ def display_logo():
82
+ print(" _____ _ _ __ __ _ ")
83
+ print(" / ____| (_) \\ \\ / / | | ")
84
+ print(" | (___ | |_ _ __ ___ __\\ \\ /\\ / /__| |__ ")
85
+ print(" ___ \\| | | '_ ` _ \\ / _ \\ \\/ \\/ / _ \\ '_ \\ ")
86
+ print(" ____) | | | | | | | | __/\\ /\\ / __/ |_) |")
87
+ print(" |_____/|_|_|_| |_| |_|\\___| \\/ \\/ \\___|_.__/ ")
88
+ print("Version: 0.1.0\t\t\t Author: S.Abilash")
89
+
90
+
91
+ def main():
92
+ display_logo()
93
+ args = sys.argv[1:]
94
+
95
+ if not args:
96
+ print("Usage:")
97
+ print(" slime new <project_name>")
98
+ print(" slime run <script>")
99
+ sys.exit(1)
100
+
101
+ command = args[0]
102
+
103
+ if command == "new":
104
+ if len(args) != 2:
105
+ print("Usage: slime new <project_name>")
106
+ sys.exit(1)
107
+
108
+ create_project(args[1])
109
+
110
+ elif command == "run":
111
+ if len(args) != 2:
112
+ print("Usage: slime run <script>")
113
+ sys.exit(1)
114
+
115
+ parent_path = Path(args[0]).parent
116
+ project_path = parent_path.joinpath(Path(args[1]))
117
+ run_project(project_path)
118
+
119
+ else:
120
+ print(f"Unknown command: {command}")
121
+
122
+
123
+ if __name__ == "__main__":
124
+ main()
@@ -0,0 +1,197 @@
1
+ # AUTHOR: S.ABILASH
2
+ # Email: abinix01@gmail.com
3
+
4
+ import inspect
5
+ from typing import Callable, Dict
6
+
7
+
8
+ class Routes:
9
+ def __init__(
10
+ self,
11
+ path: str = "/",
12
+ method: str = "GET",
13
+ stream: str | None = None,
14
+ ws: bool = False,
15
+ ) -> None:
16
+ if method.upper() not in ["GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"]:
17
+ raise ValueError(f"{method} is not Valid")
18
+
19
+ self.path: str = path
20
+ self.method: str = method
21
+ self.stream: str | None = stream
22
+ self.ws: bool = ws
23
+
24
+ def __hash__(self) -> int:
25
+ return hash((self.path, self.method, self.stream))
26
+
27
+ def __eq__(self, value: object) -> bool:
28
+ return (
29
+ isinstance(value, Routes)
30
+ and self.path == value.path
31
+ and self.method == value.method
32
+ and self.stream == value.stream
33
+ and self.ws == value.ws
34
+ )
35
+
36
+ def __str__(self) -> str:
37
+ return f"""
38
+ Path: {self.path}
39
+ Method: {self.method}
40
+ {f"Stream: {self.stream}" if self.stream is not None else ""}
41
+ {f"Websocket: {self.ws}" if self.ws is not None else ""}
42
+ """
43
+
44
+
45
+ class Slime:
46
+ def __init__(self, filename: str) -> None:
47
+ if filename is None or not isinstance(filename, str):
48
+ raise ValueError("Need argument as __file__ ")
49
+
50
+ # for templating purpose we can fetch file path with /templates and /static
51
+ self.__filename: str | None = filename
52
+
53
+ # => you can multiple or same path with different request
54
+ # like let saay user can assign a path /name
55
+ # with methods like GET,POST under /name
56
+ # => for each handler there can be only one method
57
+ self.__routes: Dict[Routes, Dict[str, Callable | None]] = {}
58
+
59
+ def middle_before(self, path: str = "/", method: str = "GET") -> Callable:
60
+ def wrapper(middle_handler) -> Callable:
61
+ if middle_handler is None or not callable(middle_handler):
62
+ raise ValueError(
63
+ f"Middleware handler should be a function for [Path: {path}, Method: {method}]"
64
+ )
65
+ if inspect.iscoroutinefunction(middle_handler):
66
+ raise Exception("Slime currently doesnt support async handler.")
67
+ found: bool = False
68
+ if path == "*":
69
+ for route in self.__routes:
70
+ call_handler = self.__routes.get(route)
71
+ if call_handler is not None:
72
+ call_handler["before"] = middle_handler
73
+ else:
74
+ for route in self.__routes:
75
+ if route.path == path and route.method == method:
76
+ call_handler = self.__routes.get(route)
77
+ if call_handler is not None:
78
+ call_handler["before"] = middle_handler
79
+ found = True
80
+ break
81
+ if not found:
82
+ raise ValueError(
83
+ "You need to define the request handler to declare middleware"
84
+ )
85
+ return middle_handler
86
+
87
+ return wrapper
88
+
89
+ def middle_after(self, path: str = "/", method: str = "GET") -> Callable:
90
+ def wrapper(middle_handler) -> Callable:
91
+ if middle_handler is None or not callable(middle_handler):
92
+ raise ValueError(
93
+ f"Middleware handler should be a function for [Path: {path}, Method: {method}]"
94
+ )
95
+ if inspect.iscoroutinefunction(middle_handler):
96
+ raise Exception("Slime currently doesnt support async handler.")
97
+ found: bool = False
98
+ if path == "*":
99
+ for route in self.__routes:
100
+ call_handler = self.__routes.get(route)
101
+ if call_handler is not None:
102
+ call_handler["before"] = middle_handler
103
+ else:
104
+ for route in self.__routes:
105
+ if route.path == path and route.method == method:
106
+ call_handler = self.__routes.get(route)
107
+ if call_handler is not None:
108
+ call_handler["after"] = middle_handler
109
+ found = True
110
+ break
111
+ if not found:
112
+ raise ValueError(
113
+ "You need to define the request handler to declare middleware"
114
+ )
115
+ return middle_handler
116
+
117
+ return wrapper
118
+
119
+ def route(
120
+ self,
121
+ path: str = "/",
122
+ method: str = "GET",
123
+ stream: str | None = None,
124
+ ws: bool = False,
125
+ ) -> Callable:
126
+ def wrapper(route_handler) -> Callable:
127
+ if route_handler is None or not callable(route_handler):
128
+ raise ValueError(
129
+ f"Route handler should be a function for [Path: {path}, Method: {method}]"
130
+ )
131
+ if inspect.iscoroutinefunction(route_handler):
132
+ raise Exception("Slime currently doesnt support async handler.")
133
+ self.__routes[Routes(path, method, stream, ws)] = {
134
+ "handler": route_handler,
135
+ "before": None,
136
+ "after": None,
137
+ }
138
+ return route_handler
139
+
140
+ return wrapper
141
+
142
+ def stream(
143
+ self, path: str = "/", method: str = "GET", content: str = "text/plain"
144
+ ) -> Callable:
145
+ def wrapper(stream_handler) -> Callable:
146
+ if stream_handler is None or not callable(stream_handler):
147
+ raise ValueError(
148
+ f"Stream handler should be a function for [Path: {path}, Method: {method}]"
149
+ )
150
+ if not isinstance(content, str):
151
+ raise ValueError(
152
+ f"Stream content type should be of type <String> with MIME for [Path: {path}, Method: {method}]"
153
+ )
154
+ self.__routes[Routes(path, method, stream=content)] = {
155
+ "handler": stream_handler,
156
+ "before": None,
157
+ "after": None,
158
+ }
159
+ return stream_handler
160
+
161
+ return wrapper
162
+
163
+ def websocket(self, path: str = "/", method: str = "GET") -> Callable:
164
+ def wrapper(websocket_handler) -> Callable:
165
+ if websocket_handler is None or not callable(websocket_handler):
166
+ raise ValueError(
167
+ f"Websocket handler should be a function for [Path: {path}, Method: {method}]"
168
+ )
169
+ self.__routes[Routes(path, method, stream=None, ws=True)] = {
170
+ "handler": websocket_handler,
171
+ "before": None,
172
+ "after": None,
173
+ }
174
+ return websocket_handler
175
+
176
+ return wrapper
177
+
178
+ def _get_routes(self) -> Dict[Routes, Dict[str, Callable | None]]:
179
+ return self.__routes
180
+
181
+ def serve(
182
+ self,
183
+ host: str = "127.0.0.1",
184
+ port: int = 3000,
185
+ secret_key: str | None = None,
186
+ dev: bool = False,
187
+ ) -> None:
188
+ if secret_key is None:
189
+ import secrets
190
+
191
+ secret_key = secrets.token_urlsafe(30)
192
+
193
+ import web
194
+
195
+ web.init_web(self, host, port, secret_key, dev)
196
+ print("Slime server is shutting down...")
197
+ print("Finished")
@@ -0,0 +1,5 @@
1
+ from .web import *
2
+
3
+ __doc__ = web.__doc__
4
+ if hasattr(web, "__all__"):
5
+ __all__ = web.__all__