Skip to content

Commit 96a5c13

Browse files
committed
feat: start adding MVC Architecture
1 parent 58a9a06 commit 96a5c13

File tree

10 files changed

+159
-59
lines changed

10 files changed

+159
-59
lines changed

Doxyfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ PROJECT_NAME = "pyEchoNext"
4848
# could be handy for archiving the generated documentation or if some version
4949
# control system is used.
5050

51-
PROJECT_NUMBER = "0.6.11"
51+
PROJECT_NUMBER = "0.6.12"
5252

5353
# Using the PROJECT_BRIEF tag one can provide an optional one line description
5454
# for a project that appears at the top of each page and should give viewer a

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Welcome to **EchoNext**, where innovation meets simplicity! Are you tired of the
4545

4646
**Imagine** a lightweight framework that empowers you to create modern web applications with lightning speed and flexibility. With EchoNext, you're not just coding; you're building a masterpiece!
4747

48-
> Last stable version: 0.6.11 alpha
48+
> Last stable version: 0.6.12 alpha
4949
5050
> Next Big Update: ASYNC & unicorn support
5151

SECURITY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ currently being supported with security updates.
88
| Version | Supported |
99
| ------- | ------------------ |
1010
| 0.6.11 | :white_check_mark: |
11+
| 0.6.11 | :white_check_mark: |
1112
| 0.6.10 | :white_check_mark: |
1213
| 0.6.9 | :white_check_mark: |
1314
| 0.5.9 | :white_check_mark: |

pyechonext/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from rich import print
2323
from rich.traceback import install
2424

25-
__version__ = "0.6.11"
25+
__version__ = "0.6.12"
2626
install(show_locals=True)
2727

2828

pyechonext/app.py

Lines changed: 7 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
WebError,
2626
)
2727
from pyechonext.views import View
28+
from pyechonext.mvc.routes import Router
2829

2930

3031
class ApplicationType(Enum):
@@ -55,7 +56,7 @@ class EchoNext:
5556
"middlewares",
5657
"application_type",
5758
"urls",
58-
"routes",
59+
"router",
5960
"i18n_loader",
6061
"l10n_loader",
6162
"history",
@@ -95,9 +96,8 @@ def __init__(
9596
self.application_type = application_type
9697
self.static_files = static_files
9798
self.static_files_manager = StaticFilesManager(self.static_files)
98-
self.routes = {}
99-
10099
self.urls = urls
100+
self.router = Router(self.urls)
101101
self.main_cache = InMemoryCache(timeout=60 * 10)
102102
self.history: List[HistoryEntry] = []
103103
self.i18n_loader = JSONi18nLoader(
@@ -114,24 +114,6 @@ def __init__(
114114

115115
logger.debug(f"Application {self.application_type.value}: {self.app_name}")
116116

117-
def _find_view(self, raw_url: str) -> Union[Type[URL], None]:
118-
"""
119-
Finds a view by raw url.
120-
121-
:param raw_url: The raw url
122-
:type raw_url: str
123-
124-
:returns: URL dataclass
125-
:rtype: Type[URL]
126-
"""
127-
url = _prepare_url(raw_url)
128-
129-
for path in self.urls:
130-
if url == _prepare_url(path.url):
131-
return path
132-
133-
return None
134-
135117
def _check_request_method(self, view: View, request: Request):
136118
"""
137119
Check request method for view
@@ -146,20 +128,6 @@ def _check_request_method(self, view: View, request: Request):
146128
if not hasattr(view, request.method.lower()):
147129
raise MethodNotAllow(f"Method not allow: {request.method}")
148130

149-
def _get_view(self, request: Request) -> View:
150-
"""
151-
Gets the view.
152-
153-
:param request: The request
154-
:type request: Request
155-
156-
:returns: The view.
157-
:rtype: View
158-
"""
159-
url = request.path
160-
161-
return self._find_view(url)
162-
163131
def _get_request(self, environ: dict) -> Request:
164132
"""
165133
Gets the request.
@@ -191,9 +159,6 @@ def route_page(self, page_path: str) -> Callable:
191159
:returns: wrapper handler
192160
:rtype: Callable
193161
"""
194-
if page_path in self.routes:
195-
raise RoutePathExistsError("Such route already exists.")
196-
197162
def wrapper(handler):
198163
"""
199164
Wrapper for handler
@@ -204,7 +169,7 @@ def wrapper(handler):
204169
:returns: handler
205170
:rtype: callable
206171
"""
207-
self.routes[page_path] = handler
172+
self.router.add_page_route(page_path, handler)
208173
return handler
209174

210175
return wrapper
@@ -216,7 +181,7 @@ def _apply_middleware_to_request(self, request: Request):
216181
:param request: The request
217182
:type request: Request
218183
"""
219-
for middleware in self.middlewares:
184+
for middleware in self.middlewares[::-1]:
220185
middleware().to_request(request)
221186

222187
def _apply_middleware_to_response(self, response: Response):
@@ -226,7 +191,7 @@ def _apply_middleware_to_response(self, response: Response):
226191
:param response: The response
227192
:type response: Response
228193
"""
229-
for middleware in self.middlewares:
194+
for middleware in self.middlewares[::-1]:
230195
middleware().to_response(response)
231196

232197
def _default_response(self, response: Response, error: WebError) -> None:
@@ -254,20 +219,7 @@ def _find_handler(self, request: Request) -> Tuple[Callable, str]:
254219
if self.static_files_manager.serve_static_file(url):
255220
return self._serve_static_file, {}
256221

257-
for path, handler in self.routes.items():
258-
parse_result = parse(path, url)
259-
if parse_result is not None:
260-
return handler, parse_result.named
261-
262-
view = self._get_view(request)
263-
264-
if view is not None:
265-
parse_result = parse(view.url, url)
266-
267-
if parse_result is not None:
268-
return view.view, parse_result.named
269-
270-
return None, None
222+
return self.router.resolve(request)
271223

272224
def get_and_save_cache_item(self, key: str, value: Any) -> Any:
273225
"""

pyechonext/mvc/controllers.py

Whitespace-only changes.

pyechonext/mvc/models.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from abc import ABC, abstractmethod
2+
from typing import Any, Union
3+
4+
5+
class BaseView(ABC):
6+
def __init__(self)

pyechonext/mvc/routes.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
from typing import Callable, Optional, List, Tuple, Dict, Union
2+
from enum import Enum
3+
from dataclasses import dataclass
4+
from parse import parse
5+
# from pyechonext.mvc.controllers import BaseController
6+
from pyechonext.views import View
7+
from pyechonext.urls import URL
8+
from pyechonext.utils.exceptions import RoutePathExistsError
9+
from pyechonext.request import Request
10+
from pyechonext.utils import _prepare_url
11+
from pyechonext.utils.exceptions import (
12+
MethodNotAllow,
13+
RoutePathExistsError,
14+
TeapotError,
15+
URLNotFound,
16+
WebError,
17+
)
18+
19+
20+
class RoutesTypes(Enum):
21+
"""
22+
This class describes routes types.
23+
"""
24+
25+
URL = 0
26+
PAGE = 1
27+
28+
29+
@dataclass
30+
class Route:
31+
"""
32+
This class describes a route.
33+
"""
34+
35+
page_path: str
36+
handler: Callable | View
37+
route_type: RoutesTypes
38+
39+
40+
def _create_url_route(url: URL) -> Route:
41+
"""
42+
Creates an url route.
43+
44+
:param url: The url
45+
:type url: URL
46+
47+
:returns: Route dataclass object
48+
:rtype: Route
49+
"""
50+
return Route(page_path=url.url, handler=url.view, route_type=RoutesTypes.URL)
51+
52+
53+
def _create_page_route(page_path: str, handler: Callable) -> Route:
54+
"""
55+
Creates a page route.
56+
57+
:param page_path: The page path
58+
:type page_path: str
59+
:param handler: The handler
60+
:type handler: Callable
61+
62+
:returns: Route dataclass object
63+
:rtype: Route
64+
"""
65+
return Route(page_path=page_path, handler=handler, route_type=RoutesTypes.PAGE)
66+
67+
68+
class Router:
69+
"""
70+
This class describes a router.
71+
"""
72+
73+
def __init__(self, urls: Optional[List[URL]] = []):
74+
"""
75+
Constructs a new instance.
76+
77+
:param urls: The urls
78+
:type urls: Array
79+
"""
80+
self.urls = urls
81+
self.routes = {}
82+
83+
self._prepare_urls()
84+
85+
def _prepare_urls(self):
86+
"""
87+
Prepare URLs (add to routes)
88+
"""
89+
for url in self.urls:
90+
self.routes[url.url] = _create_url_route(url)
91+
92+
def add_page_route(self, page_path: str, handler: Callable):
93+
"""
94+
Adds a page route.
95+
96+
:param page_path: The page path
97+
:type page_path: str
98+
:param handler: The handler
99+
:type handler: Callable
100+
101+
:raises RoutePathExistsError: Such route already exists
102+
"""
103+
if page_path in self.routes:
104+
raise RoutePathExistsError(f'Route "{page_path}" already exists.')
105+
106+
self.routes[page_path] = _create_page_route(page_path, handler)
107+
108+
def add_url(self, url: URL):
109+
"""
110+
Adds an url.
111+
112+
:param url: The url
113+
:type url: URL
114+
"""
115+
self.routes[url.url] = _create_url_route(url)
116+
117+
def resolve(self, request: Request, raise_404: Optional[bool] = True) -> Union[Tuple[Callable, Dict], None]:
118+
"""
119+
Resolve path from request
120+
121+
:param request: The request
122+
:type request: Request
123+
:param raise_404: Indicates if the 404 is raised
124+
:type raise_404: bool
125+
126+
:returns: handler and named OR raise URLNotFound (if raise_404) OR None
127+
:rtype: Union[Tuple[Callable, Dict], None]:
128+
129+
:raises URLNotFound: URL Not Found
130+
"""
131+
url = _prepare_url(request.path)
132+
133+
for path, route in self.routes.items():
134+
parse_result = parse(path, url)
135+
if parse_result is not None:
136+
return route.handler, parse_result.named
137+
138+
if raise_404:
139+
raise URLNotFound(f'URL "{url}" not found.')
140+
else:
141+
return None, None

pyechonext/mvc/views.py

Whitespace-only changes.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "pyechonext"
3-
version = "0.6.11"
3+
version = "0.6.12"
44
description = "EchoNext is a lightweight, fast and scalable web framework for Python"
55
authors = ["alexeev-prog <[email protected]>"]
66
license = "LGPL-2.1"

0 commit comments

Comments
 (0)