MahmudCore 0.0.1__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.
File without changes
@@ -0,0 +1,127 @@
1
+
2
+ import os
3
+ import inspect
4
+
5
+ from parse import parse
6
+ from webob import Request
7
+ from .response import Response
8
+ from whitenoise import WhiteNoise
9
+ from requests import Session as RequestsSession
10
+ from jinja2 import Environment, FileSystemLoader
11
+ from wsgiadapter import WSGIAdapter as RequestsWSGIAdapter
12
+
13
+ from .middleware import Middleware
14
+
15
+
16
+ class API:
17
+ def __init__(self, templates_dir="templates", static_dir="static"):
18
+ self.routes = {}
19
+ self.exception_handler = None
20
+
21
+ self.templates_env = Environment(
22
+ loader=FileSystemLoader(os.path.abspath(templates_dir))
23
+ )
24
+
25
+ self.whitenoise = WhiteNoise(self.wsgi_app, root=static_dir)
26
+ self.middleware = Middleware(self)
27
+
28
+
29
+ def __call__(self, environ, start_response):
30
+ path_info = environ["PATH_INFO"]
31
+
32
+ if path_info.startswith("/static"):
33
+ environ["PATH_INFO"] = path_info[len("/static"):]
34
+ return self.whitenoise(environ, start_response)
35
+
36
+ return self.middleware(environ, start_response)
37
+
38
+
39
+ def add_middleware(self, middleware_cls):
40
+ self.middleware.add(middleware_cls)
41
+
42
+
43
+ def wsgi_app(self, environ, start_response):
44
+ request = Request(environ)
45
+ response = self.handle_request(request)
46
+ return response(environ, start_response)
47
+
48
+
49
+ def add_route(self, path, handler, allowed_methods=None):
50
+ assert path not in self.routes, f"Route '{path}' already exists."
51
+
52
+ if allowed_methods is None:
53
+ allowed_methods = ["get", "post", "put", "patch", "delete", "options"]
54
+
55
+ self.routes[path] = {
56
+ "handler": handler,
57
+ "allowed_methods": [method.lower() for method in allowed_methods]
58
+ }
59
+
60
+
61
+ def route(self, path, allowed_methods=None):
62
+ def wrapper(handler):
63
+ self.add_route(path, handler, allowed_methods) # Reuse add_route logic
64
+ return handler
65
+ return wrapper
66
+
67
+
68
+ def find_handler(self, request_path):
69
+ for path, handler_data in self.routes.items():
70
+ parse_result = parse(path, request_path)
71
+ if parse_result is not None:
72
+ return handler_data, parse_result.named
73
+ return None, None
74
+
75
+
76
+ def handle_request(self, request):
77
+ response = Response()
78
+ handler_data, kwargs = self.find_handler(request_path=request.path)
79
+
80
+ try:
81
+ if handler_data is not None:
82
+ handler = handler_data["handler"]
83
+ allowed_methods = handler_data["allowed_methods"]
84
+
85
+ if inspect.isclass(handler):
86
+ # class-based handler — pick method by HTTP verb
87
+ handler = getattr(handler(), request.method.lower(), None)
88
+ if handler is None:
89
+ raise AttributeError("Method not allowed", request.method)
90
+ else:
91
+ # function-based handler — check allowed methods list
92
+ if request.method.lower() not in allowed_methods:
93
+ raise AttributeError("Method not allowed", request.method)
94
+
95
+ handler(request, response, **kwargs)
96
+
97
+ else:
98
+ self.default_response(response)
99
+
100
+ except Exception as e:
101
+ if self.exception_handler is None:
102
+ raise e
103
+ else:
104
+ self.exception_handler(request, response, e)
105
+
106
+ return response
107
+
108
+
109
+ def default_response(self, response):
110
+ response.status_code = 404
111
+ response.text = "Request Not found"
112
+
113
+
114
+ def add_exception_handler(self, exception_handler):
115
+ self.exception_handler = exception_handler
116
+
117
+
118
+ def template(self, template_name, context=None):
119
+ if context is None:
120
+ context = {}
121
+ return self.templates_env.get_template(template_name).render(**context)
122
+
123
+
124
+ def test_session(self, base_url="http://127.0.0.1:8082"):
125
+ session = RequestsSession()
126
+ session.mount(prefix=base_url, adapter=RequestsWSGIAdapter(self))
127
+ return session
@@ -0,0 +1,27 @@
1
+
2
+ from webob import Request
3
+
4
+ class Middleware:
5
+ def __init__(self, app):
6
+ self.app = app
7
+
8
+ def __call__(self, environ, start_response):
9
+ request = Request(environ)
10
+ response = self.handle_request(request)
11
+ return response(environ, start_response)
12
+
13
+ def add(self, middleware_cls):
14
+ self.app = middleware_cls(self.app)
15
+
16
+ def process_request(self, req):
17
+ pass
18
+
19
+ def process_response(self, req, resp):
20
+ pass
21
+
22
+ def handle_request(self, request):
23
+ self.process_request(request)
24
+ response = self.app.handle_request(request)
25
+ self.process_response(request, response)
26
+
27
+ return response
@@ -0,0 +1,40 @@
1
+
2
+ import json
3
+ from webob import Response as WebObResponse
4
+
5
+
6
+ class Response:
7
+ def __init__(self):
8
+ self.json = None
9
+ self.html = None
10
+ self.text = None
11
+ self.content_type = None
12
+ self.body = b''
13
+ self.status_code = 200
14
+ self.headers = {}
15
+
16
+ def __call__(self, environ, start_response):
17
+ self.set_body_and_content_type()
18
+
19
+ response = WebObResponse(
20
+ body=self.body, content_type=self.content_type, status=self.status_code
21
+ )
22
+
23
+ # Apply any custom headers
24
+ for key, value in self.headers.items():
25
+ response.headers[key] = value
26
+
27
+ return response(environ, start_response)
28
+
29
+ def set_body_and_content_type(self):
30
+ if self.json is not None:
31
+ self.body = json.dumps(self.json).encode("UTF-8")
32
+ self.content_type = "application/json"
33
+
34
+ if self.html is not None:
35
+ self.body = self.html.encode()
36
+ self.content_type = "text/html"
37
+
38
+ if self.text is not None:
39
+ self.body = self.text.encode()
40
+ self.content_type = "text/plain"
@@ -0,0 +1,49 @@
1
+ Metadata-Version: 2.4
2
+ Name: MahmudCore
3
+ Version: 0.0.1
4
+ Summary: MahmudCore — a Python web framework built from scratch for learning core web framework concepts
5
+ Home-page: https://github.com/dear-mahmud-bd/python-framework-from-scratch
6
+ Author: Md. Mahmudul Hasan
7
+ Author-email: dearmahmud.bd@gmail.com
8
+ License: MIT
9
+ Keywords: wsgi web framework python learning
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Education
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
21
+ Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
22
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
23
+ Requires-Python: >=3.8.0
24
+ Description-Content-Type: text/markdown
25
+ Requires-Dist: webob==1.8.9
26
+ Requires-Dist: parse==1.21.1
27
+ Requires-Dist: requests==2.33.1
28
+ Requires-Dist: requests-wsgi-adapter==0.4.1
29
+ Requires-Dist: Jinja2==3.1.6
30
+ Requires-Dist: whitenoise==6.12.0
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest; extra == "dev"
33
+ Requires-Dist: pytest-cov; extra == "dev"
34
+ Requires-Dist: gunicorn; extra == "dev"
35
+ Requires-Dist: waitress; extra == "dev"
36
+ Dynamic: author
37
+ Dynamic: author-email
38
+ Dynamic: classifier
39
+ Dynamic: description
40
+ Dynamic: description-content-type
41
+ Dynamic: home-page
42
+ Dynamic: keywords
43
+ Dynamic: license
44
+ Dynamic: provides-extra
45
+ Dynamic: requires-dist
46
+ Dynamic: requires-python
47
+ Dynamic: summary
48
+
49
+ MahmudCore — a Python web framework built from scratch for learning core web framework concepts
@@ -0,0 +1,10 @@
1
+ setup.py
2
+ MahmudCore/__init__.py
3
+ MahmudCore/api.py
4
+ MahmudCore/middleware.py
5
+ MahmudCore/response.py
6
+ MahmudCore.egg-info/PKG-INFO
7
+ MahmudCore.egg-info/SOURCES.txt
8
+ MahmudCore.egg-info/dependency_links.txt
9
+ MahmudCore.egg-info/requires.txt
10
+ MahmudCore.egg-info/top_level.txt
@@ -0,0 +1,12 @@
1
+ webob==1.8.9
2
+ parse==1.21.1
3
+ requests==2.33.1
4
+ requests-wsgi-adapter==0.4.1
5
+ Jinja2==3.1.6
6
+ whitenoise==6.12.0
7
+
8
+ [dev]
9
+ pytest
10
+ pytest-cov
11
+ gunicorn
12
+ waitress
@@ -0,0 +1 @@
1
+ MahmudCore
@@ -0,0 +1,49 @@
1
+ Metadata-Version: 2.4
2
+ Name: MahmudCore
3
+ Version: 0.0.1
4
+ Summary: MahmudCore — a Python web framework built from scratch for learning core web framework concepts
5
+ Home-page: https://github.com/dear-mahmud-bd/python-framework-from-scratch
6
+ Author: Md. Mahmudul Hasan
7
+ Author-email: dearmahmud.bd@gmail.com
8
+ License: MIT
9
+ Keywords: wsgi web framework python learning
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Education
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
21
+ Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
22
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
23
+ Requires-Python: >=3.8.0
24
+ Description-Content-Type: text/markdown
25
+ Requires-Dist: webob==1.8.9
26
+ Requires-Dist: parse==1.21.1
27
+ Requires-Dist: requests==2.33.1
28
+ Requires-Dist: requests-wsgi-adapter==0.4.1
29
+ Requires-Dist: Jinja2==3.1.6
30
+ Requires-Dist: whitenoise==6.12.0
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest; extra == "dev"
33
+ Requires-Dist: pytest-cov; extra == "dev"
34
+ Requires-Dist: gunicorn; extra == "dev"
35
+ Requires-Dist: waitress; extra == "dev"
36
+ Dynamic: author
37
+ Dynamic: author-email
38
+ Dynamic: classifier
39
+ Dynamic: description
40
+ Dynamic: description-content-type
41
+ Dynamic: home-page
42
+ Dynamic: keywords
43
+ Dynamic: license
44
+ Dynamic: provides-extra
45
+ Dynamic: requires-dist
46
+ Dynamic: requires-python
47
+ Dynamic: summary
48
+
49
+ MahmudCore — a Python web framework built from scratch for learning core web framework concepts
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,104 @@
1
+ import io
2
+ import os
3
+
4
+ from setuptools import find_packages, setup
5
+
6
+ # ─── Package metadata ─────────────────────────────────────────────────────────
7
+
8
+ NAME = "MahmudCore"
9
+ DESCRIPTION = "MahmudCore — a Python web framework built from scratch for learning core web framework concepts"
10
+ URL = "https://github.com/dear-mahmud-bd/python-framework-from-scratch"
11
+ EMAIL = "dearmahmud.bd@gmail.com"
12
+ AUTHOR = "Md. Mahmudul Hasan"
13
+ REQUIRES_PYTHON = ">=3.8.0" # 3.6 and 3.7 are end-of-life, no point supporting them
14
+ VERSION = "0.0.1"
15
+
16
+ # ─── Runtime dependencies ─────────────────────────────────────────────────────
17
+ # These get installed automatically when someone does: pip install mahmudcore
18
+
19
+ REQUIRED = [
20
+ "webob==1.8.9",
21
+ "parse==1.21.1",
22
+ "requests==2.33.1",
23
+ "requests-wsgi-adapter==0.4.1",
24
+ "Jinja2==3.1.6",
25
+ "whitenoise==6.12.0",
26
+ ]
27
+
28
+ # ─── Optional / development dependencies ──────────────────────────────────────
29
+ # Install with: pip install mahmudcore[dev]
30
+
31
+ EXTRAS = {
32
+ "dev": [
33
+ "pytest",
34
+ "pytest-cov",
35
+ "gunicorn",
36
+ "waitress",
37
+ ]
38
+ }
39
+
40
+ # ─── Read long description from README ────────────────────────────────────────
41
+
42
+ here = os.path.abspath(os.path.dirname(__file__))
43
+
44
+ try:
45
+ with io.open(os.path.join(here, "README.md"), encoding="utf-8") as f:
46
+ long_description = "\n" + f.read()
47
+ except FileNotFoundError:
48
+ long_description = DESCRIPTION
49
+
50
+ # ─── Version resolution ───────────────────────────────────────────────────────
51
+
52
+ about = {}
53
+ if not VERSION:
54
+ # read version from mahmudcore/__version__.py if VERSION not set above
55
+ project_slug = NAME.lower().replace("-", "_").replace(" ", "_")
56
+ with open(os.path.join(here, project_slug, "__version__.py")) as f:
57
+ exec(f.read(), about)
58
+ else:
59
+ about["__version__"] = VERSION
60
+
61
+ # ─── Setup ────────────────────────────────────────────────────────────────────
62
+
63
+ setup(
64
+ name=NAME,
65
+ version=about["__version__"],
66
+ description=DESCRIPTION,
67
+ long_description=long_description,
68
+ long_description_content_type="text/markdown",
69
+ author=AUTHOR,
70
+ author_email=EMAIL,
71
+ url=URL, # ← added
72
+ python_requires=REQUIRES_PYTHON,
73
+ packages=find_packages(exclude=["test_*", "tests*"]),
74
+ install_requires=REQUIRED,
75
+ extras_require=EXTRAS, # ← added dev dependencies
76
+ include_package_data=True,
77
+ license="MIT",
78
+ classifiers=[
79
+ # project maturity
80
+ "Development Status :: 3 - Alpha",
81
+
82
+ # who it's for
83
+ "Intended Audience :: Developers",
84
+ "Intended Audience :: Education", # ← added, since this is a learning framework
85
+
86
+ # license
87
+ "License :: OSI Approved :: MIT License",
88
+
89
+ # python versions supported
90
+ "Programming Language :: Python :: 3",
91
+ "Programming Language :: Python :: 3.8",
92
+ "Programming Language :: Python :: 3.9",
93
+ "Programming Language :: Python :: 3.10",
94
+ "Programming Language :: Python :: 3.11",
95
+ "Programming Language :: Python :: 3.12",
96
+
97
+ # what it does
98
+ "Topic :: Internet :: WWW/HTTP :: WSGI",
99
+ "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
100
+ "Topic :: Software Development :: Libraries :: Application Frameworks", # ← added
101
+ ],
102
+ setup_requires=["wheel"],
103
+ keywords="wsgi web framework python learning", # ← added, helps PyPI search
104
+ )