GNServer 0.0.0.0.7__py3-none-any.whl → 0.0.0.0.9__py3-none-any.whl

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.
GNServer/_app.py CHANGED
@@ -110,6 +110,59 @@ def guess_type(filename: str) -> str:
110
110
  return mime_map.get(ext, "application/octet-stream")
111
111
 
112
112
 
113
+ import re
114
+ from typing import List
115
+
116
+ # regex для !{var}, поддерживает вложенность через точку
117
+ TPL_VAR_RE = re.compile(r'(?<!\\)!\{([A-Za-z_][A-Za-z0-9_\.]*)\}')
118
+
119
+ # список mime, которые считаем текстовыми
120
+ TEXTUAL_MIME_PREFIXES = [
121
+ "text/", # text/html, text/css, text/plain
122
+ ]
123
+ TEXTUAL_MIME_EXACT = {
124
+ "application/javascript",
125
+ "application/json",
126
+ "application/xml",
127
+ "application/xhtml+xml"
128
+ }
129
+ TEXTUAL_MIME_SUFFIXES = (
130
+ "+xml", # например application/rss+xml
131
+ "+json", # application/ld+json
132
+ )
133
+
134
+ def extract_template_vars(filedata: bytes, mime: str) -> List[str]:
135
+ """
136
+ Ищет все !{var} в тексте, если MIME относится к текстовым.
137
+ """
138
+ mime = (mime or "").lower().strip()
139
+
140
+ # определяем, текстовый ли mime
141
+ is_textual = (
142
+ mime.startswith(tuple(TEXTUAL_MIME_PREFIXES))
143
+ or mime in TEXTUAL_MIME_EXACT
144
+ or mime.endswith(TEXTUAL_MIME_SUFFIXES)
145
+ or "javascript" in mime
146
+ or "json" in mime
147
+ or "xml" in mime
148
+ )
149
+
150
+ if not is_textual:
151
+ return []
152
+
153
+ try:
154
+ text = filedata.decode("utf-8", errors="ignore")
155
+ except Exception:
156
+ return []
157
+
158
+ return list(set(m.group(1) for m in TPL_VAR_RE.finditer(text)))
159
+
160
+
161
+
162
+
163
+
164
+
165
+
113
166
  @dataclass
114
167
  class Route:
115
168
  method: str
@@ -288,9 +341,9 @@ class App:
288
341
  return gn.GNResponse("gn:backend:404", {'error': 'Not Found'})
289
342
 
290
343
 
291
- def fastFile(self, path: str, file_path: str, cors: Optional[gn.CORSObject] = None):
344
+ def fastFile(self, path: str, file_path: str, cors: Optional[gn.CORSObject] = None, template: Optional[gn.TemplateObject] = None, payload: Optional[dict] = None):
292
345
  @self.get(path)
293
- async def r_static(path: str):
346
+ async def r_static():
294
347
  nonlocal file_path
295
348
  if file_path.endswith('/'):
296
349
  file_path = file_path[:-1]
@@ -298,13 +351,11 @@ class App:
298
351
  if not os.path.isfile(file_path):
299
352
  return gn.GNResponse('gn:backend:404', cors=cors)
300
353
 
301
- mime_type = guess_type(file_path.split('/')[-1])
302
- async with aiofiles.open(file_path, "rb") as f:
303
- data = await f.read()
354
+ fileObject = gn.FileObject(file_path, template)
355
+ return gn.GNResponse('ok', payload=payload, files=fileObject, cors=cors)
304
356
 
305
- return gn.GNResponse('ok', {'files': [{'mime-type': mime_type, 'data': data}]}, cors=cors)
306
357
 
307
- def static(self, path: str, dir_path: str, cors: Optional[gn.CORSObject] = None):
358
+ def static(self, path: str, dir_path: str, cors: Optional[gn.CORSObject] = None, template: Optional[gn.TemplateObject] = None, payload: Optional[dict] = None):
308
359
  @self.get(f"{path}/{{_path:path}}")
309
360
  async def r_static(_path: str):
310
361
  file_path = os.path.join(dir_path, _path)
@@ -314,12 +365,9 @@ class App:
314
365
 
315
366
  if not os.path.isfile(file_path):
316
367
  return gn.GNResponse('gn:backend:404', cors=cors)
317
-
318
- mime_type = guess_type(file_path.split('/')[-1])
319
- async with aiofiles.open(file_path, "rb") as f:
320
- data = await f.read()
321
-
322
- return gn.GNResponse('ok', {'files': [{'mime-type': mime_type, 'data': data}]}, cors=cors)
368
+
369
+ fileObject = gn.FileObject(file_path, template)
370
+ return gn.GNResponse('ok', payload=payload, files=fileObject, cors=cors)
323
371
 
324
372
 
325
373
 
@@ -429,10 +477,6 @@ class App:
429
477
 
430
478
  response = await self._api.dispatch(request)
431
479
 
432
-
433
- response = await self.resolve_extra_response(response)
434
-
435
-
436
480
  if inspect.isasyncgen(response):
437
481
  async for chunk in response: # type: ignore[misc]
438
482
  chunk._stream = True
@@ -455,7 +499,7 @@ class App:
455
499
  except Exception as e:
456
500
  logger.error('GNServer: error\n' + traceback.format_exc())
457
501
 
458
- response = gn.GNResponse('gn:backend:500:Internal Server Error')
502
+ response = gn.GNResponse('gn:backend:500')
459
503
  self._quic.send_stream_data(request.stream_id, response.serialize(3), end_stream=True)
460
504
  self.transmit()
461
505
 
@@ -463,36 +507,12 @@ class App:
463
507
  if response._cors is None:
464
508
  response._cors = self._api._cors
465
509
 
510
+ await response.assembly()
511
+
466
512
  return response
467
513
 
468
514
 
469
- async def resolve_extra_response(self, response: Union[gn.GNResponse, AsyncGenerator[gn.GNResponse, None]]) -> Union[gn.GNResponse, AsyncGenerator[gn.GNResponse, None]]:
470
-
471
- file_types = (
472
- 'html',
473
- 'css',
474
- 'js',
475
- 'svg'
476
- )
477
-
478
- if isinstance(response, gn.GNResponse):
479
- payload = response.payload
480
- if payload is not None:
481
- for ext_file in file_types:
482
- ext_file_ = payload.get(ext_file)
483
- if ext_file_ is not None:
484
- if isinstance(ext_file_, str):
485
- if ext_file_.startswith('/') or ext_file_.startswith('./'):
486
- try:
487
- async with await anyio.open_file(ext_file_, mode="rb") as file:
488
- data = await file.read()
489
-
490
- payload.pop(ext_file)
491
- payload['files'] = [{'mime-type': guess_type(ext_file_.split('/')[-1]), 'data': data}]
492
- except Exception as e:
493
- payload[ext_file] = f'GNServer error: {e}'
494
- logger.debug(f'error resolving extra response -> {traceback.format_exc()}')
495
- return response
515
+
496
516
 
497
517
 
498
518
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GNServer
3
- Version: 0.0.0.0.7
3
+ Version: 0.0.0.0.9
4
4
  Summary: GNServer
5
5
  Home-page: https://github.com/KeyisB/libs/tree/main/GNServer
6
6
  Author: KeyisB
@@ -0,0 +1,7 @@
1
+ GNServer/__init__.py,sha256=l9B6gZZ2gTkWvNdla--J15_Bj0BtP8RyPsrMI6amgDo,1376
2
+ GNServer/_app.py,sha256=dKgGAFVwDMGOt7i4HGF2TrVz0XSGjE0TV2hRJHfUnxA,18273
3
+ gnserver-0.0.0.0.9.dist-info/licenses/LICENSE,sha256=WH_t7dKZyWJ5Ld07eYIkUG4Tv6zZWXtAdsUqYAUesn0,1084
4
+ gnserver-0.0.0.0.9.dist-info/METADATA,sha256=Y7bXZtN9rVDJFkIuyoEIWVUWPRVYpVjvvMaVZxnRwds,804
5
+ gnserver-0.0.0.0.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
+ gnserver-0.0.0.0.9.dist-info/top_level.txt,sha256=-UOUBuD4u7Qkb1o5PdcwyA3kx8xCH2lwy0tJHi26Wb4,9
7
+ gnserver-0.0.0.0.9.dist-info/RECORD,,
@@ -1,8 +0,0 @@
1
- GNServer/__init__.py,sha256=l9B6gZZ2gTkWvNdla--J15_Bj0BtP8RyPsrMI6amgDo,1376
2
- GNServer/_app.py,sha256=w2rK4nrDWVZ_f0c3tC2cVckvkb8gY4Xudajiajw6yqQ,18534
3
- GNServer/models.py,sha256=3HTbPgXMcltK3hh3RWii0z6X2qYitXmPWzud8pOc4Rk,50
4
- gnserver-0.0.0.0.7.dist-info/licenses/LICENSE,sha256=WH_t7dKZyWJ5Ld07eYIkUG4Tv6zZWXtAdsUqYAUesn0,1084
5
- gnserver-0.0.0.0.7.dist-info/METADATA,sha256=bqObiR6rIxTCq4Lg_GU9oRyYbw6B1wg2NggJV2qrg5c,804
6
- gnserver-0.0.0.0.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
- gnserver-0.0.0.0.7.dist-info/top_level.txt,sha256=-UOUBuD4u7Qkb1o5PdcwyA3kx8xCH2lwy0tJHi26Wb4,9
8
- gnserver-0.0.0.0.7.dist-info/RECORD,,