Skip to content

Commit 2c99528

Browse files
committed
feat/docs/fix: fix request, add settings config loader, add api docs gen, fix bugs, improve docs
1 parent 559fddd commit 2c99528

25 files changed

+1469
-64
lines changed

Doxyfile

Lines changed: 2 additions & 2 deletions
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.4.2"
51+
PROJECT_NUMBER = "0.4.3"
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
@@ -1181,7 +1181,7 @@ FILTER_SOURCE_PATTERNS =
11811181
# (index.html). This can be useful if you have a project on for instance GitHub
11821182
# and want to reuse the introduction page also for the Doxygen output.
11831183

1184-
USE_MDFILE_AS_MAINPAGE =
1184+
USE_MDFILE_AS_MAINPAGE = README.md
11851185

11861186
# The Fortran standard specifies that for fixed formatted Fortran code all
11871187
# characters from position 72 are to be considered as comment. A common

SECURITY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ currently being supported with security updates.
77

88
| Version | Supported |
99
| ------- | ------------------ |
10+
| 0.4.3 | :white_check_mark: |
1011
| 0.4.2 | :white_check_mark: |
1112
| 0.4.1 | :white_check_mark: |
1213
| 0.3.1 | :white_check_mark: |

docs/ru/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
1. [Устройство веб-фреймворка](./webframework_design.md)
66
2. [Создание веб-приложения](./webapp_creation.md)
77
3. [Создание маршрутов (routes&views)](./routes_and_views.md)
8+
4. [Request/Response](./requests_responses.md)
89

910
## Дополнительные материалы
1011

docs/ru/requests_responses.md

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
# pyEchoNext / Request/Response
2+
3+
---
4+
5+
Request - HTTP-запрос. Response - HTTP-ответ.
6+
7+
## Введение
8+
В информатике запрос-ответ или запрос-реплика - это один из основных методов, используемых компьютерами для связи друг с другом в сети, при котором первый компьютер отправляет запрос на некоторые данные, а второй отвечает на запрос. Более конкретно, это шаблон обмена сообщениями, при котором запрашивающий отправляет сообщение с запросом системе-ответчику, которая получает и обрабатывает запрос, в конечном счете возвращая сообщение в ответ. Это аналогично телефонному звонку, при котором вызывающий абонент должен дождаться, пока получатель возьмет трубку, прежде чем что-либо можно будет обсудить.
9+
10+
## Request
11+
**Request** — это запрос, который содержит данные для взаимодействия между клиентом и API: базовый URL, конечную точку, используемый метод, заголовки и т. д..
12+
13+
```python
14+
class Request:
15+
"""
16+
This class describes a request.
17+
"""
18+
19+
def __init__(self, environ: dict, settings: Settings):
20+
"""
21+
Constructs a new instance.
22+
23+
:param environ: The environ
24+
:type environ: dict
25+
"""
26+
self.environ: dict = environ
27+
self.settings: Settings = settings
28+
self.method: str = self.environ["REQUEST_METHOD"]
29+
self.path: str = self.environ["PATH_INFO"]
30+
self.GET: dict = self._build_get_params_dict(self.environ["QUERY_STRING"])
31+
self.POST: dict = self._build_post_params_dict(self.environ["wsgi.input"].read())
32+
self.user_agent: str = self.environ["HTTP_USER_AGENT"]
33+
self.extra: dict = {}
34+
35+
logger.debug(f"New request created: {self.method} {self.path}")
36+
37+
def __getattr__(self, item: Any) -> Union[Any, None]:
38+
"""
39+
Magic method for get attrs (from extra)
40+
41+
:param item: The item
42+
:type item: Any
43+
44+
:returns: Item from self.extra or None
45+
:rtype: Union[Any, None]
46+
"""
47+
return self.extra.get(item, None)
48+
49+
def _build_get_params_dict(self, raw_params: str):
50+
"""
51+
Builds a get parameters dictionary.
52+
53+
:param raw_params: The raw parameters
54+
:type raw_params: str
55+
"""
56+
return parse_qs(raw_params)
57+
58+
def _build_post_params_dict(self, raw_params: bytes):
59+
"""
60+
Builds a post parameters dictionary.
61+
62+
:param raw_params: The raw parameters
63+
:type raw_params: bytes
64+
"""
65+
return parse_qs(raw_params.decode())
66+
```
67+
68+
Request требует следующие аргументы для создания:
69+
70+
+ environ (словарь) - веб-окружение (генерируется gunicorn)
71+
+ settings (объект датакласса pyechonext.config.Settings)
72+
73+
Request имеет следующие публичные атрибуты:
74+
75+
+ environ (словарь) - веб-окружение
76+
+ settings (объект датакласса pyechonext.config.Settings)
77+
+ method (строка) - http-метод
78+
+ path (строка) - путь
79+
+ GET (словарь) - параметры get-запроса
80+
+ POST (словарь) - параметры post-запроса
81+
+ user_agent (строка) - User-Agent
82+
+ extra (словарь) - дополнительные параметры (например для middleware)
83+
84+
Request также имеет следующие методы:
85+
86+
+ `__getattr__` - магический метод дескриптора для получения атрибутов (для получения элементов из атрибута extra)
87+
+ `_build_get_params_dict` - приватный метод для парсинга параметров get-запроса
88+
+ `_build_post_params_dict` - приватный метод для парсинга параметров post-запроса
89+
90+
## Response
91+
**Response** — это ответ, который содержит данные, возвращаемые сервером, в том числе контент, код состояния и заголовки.
92+
93+
```python
94+
class Response:
95+
"""
96+
This dataclass describes a response.
97+
"""
98+
99+
default_content_type: str = "application/json"
100+
default_charset: str = "UTF-8"
101+
unicode_errors: str = "strict"
102+
default_conditional_response: bool = False
103+
default_body_encoding: str = "UTF-8"
104+
105+
def __init__(
106+
self,
107+
request: Request,
108+
status_code: Optional[int] = 200,
109+
body: Optional[str] = None,
110+
headers: Optional[Dict[str, str]] = {},
111+
content_type: Optional[str] = None,
112+
charset: Optional[str] = None,
113+
):
114+
"""
115+
Constructs a new instance.
116+
117+
:param status_code: The status code
118+
:type status_code: int
119+
:param body: The body
120+
:type body: str
121+
:param headers: The headers
122+
:type headers: Dict[str, str]
123+
:param content_type: The content type
124+
:type content_type: str
125+
"""
126+
if status_code == 200:
127+
self.status_code: str = "200 OK"
128+
else:
129+
self.status_code: str = str(status_code)
130+
131+
if content_type is None:
132+
self.content_type: str = self.default_content_type
133+
else:
134+
self.content_type: str = content_type
135+
136+
if charset is None:
137+
self.charset: str = self.default_charset
138+
else:
139+
self.charset: str = charset
140+
141+
if body is not None:
142+
self.body: str = body
143+
else:
144+
self.body: str = ""
145+
146+
self._headerslist: list = headers
147+
self._added_headers: list = []
148+
self.request: Request = request
149+
self.extra: dict = {}
150+
151+
self._update_headers()
152+
153+
def __getattr__(self, item: Any) -> Union[Any, None]:
154+
"""
155+
Magic method for get attrs (from extra)
156+
157+
:param item: The item
158+
:type item: Any
159+
160+
:returns: Item from self.extra or None
161+
:rtype: Union[Any, None]
162+
"""
163+
return self.extra.get(item, None)
164+
165+
def _structuring_headers(self, environ):
166+
headers = {
167+
"Host": environ["HTTP_HOST"],
168+
"Accept": environ["HTTP_ACCEPT"],
169+
"User-Agent": environ["HTTP_USER_AGENT"],
170+
}
171+
172+
for name, value in headers.items():
173+
self._headerslist.append((name, value))
174+
175+
for header_tuple in self._added_headers:
176+
self._headerslist.append(header_tuple)
177+
178+
def _update_headers(self) -> None:
179+
"""
180+
Sets the headers by environ.
181+
182+
:param environ: The environ
183+
:type environ: dict
184+
"""
185+
self._headerslist = [
186+
("Content-Type", f"{self.content_type}; charset={self.charset}"),
187+
("Content-Length", str(len(self.body))),
188+
]
189+
190+
def add_headers(self, headers: List[Tuple[str, str]]):
191+
"""
192+
Adds new headers.
193+
194+
:param headers: The headers
195+
:type headers: List[Tuple[str, str]]
196+
"""
197+
for header in headers:
198+
self._added_headers.append(header)
199+
200+
def _encode_body(self):
201+
"""
202+
Encodes a body.
203+
"""
204+
if self.content_type.split("/")[-1] == "json":
205+
self.body = str(self.json)
206+
207+
try:
208+
self.body = self.body.encode("UTF-8")
209+
except AttributeError:
210+
self.body = str(self.body).encode("UTF-8")
211+
212+
def __call__(self, environ: dict, start_response: method) -> Iterable:
213+
"""
214+
Makes the Response object callable.
215+
216+
:param environ: The environ
217+
:type environ: dict
218+
:param start_response: The start response
219+
:type start_response: method
220+
221+
:returns: response body
222+
:rtype: Iterable
223+
"""
224+
self._encode_body()
225+
226+
self._update_headers()
227+
self._structuring_headers(environ)
228+
229+
logger.debug(
230+
f"[{environ['REQUEST_METHOD']} {self.status_code}] Run response: {self.content_type}"
231+
)
232+
233+
start_response(status=self.status_code, headers=self._headerslist)
234+
235+
return iter([self.body])
236+
237+
@property
238+
def json(self) -> dict:
239+
"""
240+
Parse request body as JSON.
241+
242+
:returns: json body
243+
:rtype: dict
244+
"""
245+
if self.body:
246+
if self.content_type.split("/")[-1] == "json":
247+
return json.dumps(self.body)
248+
else:
249+
return json.dumps(self.body.decode("UTF-8"))
250+
251+
return {}
252+
253+
def __repr__(self):
254+
"""
255+
Returns a unambiguous string representation of the object (for debug...).
256+
257+
:returns: String representation of the object.
258+
:rtype: str
259+
"""
260+
return f"<{self.__class__.__name__} at 0x{abs(id(self)):x} {self.status_code}>"
261+
```
262+
263+
Response имеет следующие аргументы:
264+
265+
+ request (объект класса Request) - запрос
266+
+ [опционально] status_code (целочисленное значение) - статус-код ответа
267+
+ [опционально] body (строка) - тело ответа
268+
+ [опционально] headers (словарь) - заголовки ответа
269+
+ [опционально] content_type (строка) - тип контента ответа
270+
+ [опционально] charset (строка) - кодировка ответа
271+
272+
Response имеет следующие атрибуты:
273+
274+
+ status_code (строка) - статус-код (по умолчанию "200 OK")
275+
+ content_type (строка) - контент-тип (по умолчанию равен значению default_content_type)
276+
+ charset (строка) - кодировка (по умолчанию равен значению default_charset)
277+
+ body (строка) - тело овтета (по умолчанию равен пустой строке)
278+
+ `_headerslist` (список) - приватный список заголовков ответа
279+
+ `_added_headers` (список) - приватный список добавленных заголовков ответа
280+
+ request (объект класса Request) - запрос
281+
+ extra (словарь) - дополнительные параметры
282+
283+
Response имеет следующие методы:
284+
285+
+ `__getattr__` - магический метод дескриптора для получения атрибутов (для получения элементов из атрибута extra)
286+
+ `_structuring_headers` - приватный метод структуирования заголовков из веб-окружения
287+
+ `_update_headers` - приватный метод обновления (перезаписывания) списков заголовков
288+
+ `add_headers` - публичный метод добавления заголовков
289+
+ `_encode_body` - кодирование тела ответа
290+
+ `__call__` - магический метод, делает объект Response вызываемым
291+
+ `json` - свойство класса для получения тела ответа в виде json
292+
293+
---
294+
295+
[Содержание](./index.md)
296+

0 commit comments

Comments
 (0)