reyserver 1.1.58__py3-none-any.whl → 1.1.59__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.

Potentially problematic release.


This version of reyserver might be problematic. Click here for more details.

reyserver/rauth.py CHANGED
@@ -9,14 +9,18 @@
9
9
  """
10
10
 
11
11
 
12
- from typing import Any, Literal
12
+ from typing import Any, TypedDict, NotRequired, Literal
13
13
  from datetime import datetime as Datetime
14
+ from re import PatternError
14
15
  from fastapi import APIRouter, Request
16
+ from fastapi.params import Depends
17
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
15
18
  from reydb import rorm, DatabaseEngine, DatabaseEngineAsync
16
- from reykit.rdata import encode_jwt, is_hash_bcrypt
19
+ from reykit.rdata import encode_jwt, decode_jwt, is_hash_bcrypt
20
+ from reykit.rre import search_batch
17
21
  from reykit.rtime import now
18
22
 
19
- from .rbase import ServerConfig, Bind, exit_api
23
+ from .rbase import Bind, exit_api
20
24
 
21
25
 
22
26
  __all__ = (
@@ -30,6 +34,34 @@ __all__ = (
30
34
  )
31
35
 
32
36
 
37
+ UserInfo = TypedDict(
38
+ 'UserInfo',
39
+ {
40
+ 'create_time': float,
41
+ 'udpate_time': float,
42
+ 'user_id': int,
43
+ 'user_name': str,
44
+ 'role_names': list[str],
45
+ 'perm_names': list[str],
46
+ 'perm_apis': list[str],
47
+ 'email': str | None,
48
+ 'phone': str | None,
49
+ 'avatar': int | None,
50
+ 'password': NotRequired[str]
51
+ }
52
+ )
53
+ Token = TypedDict(
54
+ 'Token',
55
+ {
56
+ 'sub': int,
57
+ 'iat': int,
58
+ 'nbf': int,
59
+ 'exp': int,
60
+ 'user': UserInfo
61
+ }
62
+ )
63
+
64
+
33
65
  class DatabaseORMTableUser(rorm.Table):
34
66
  """
35
67
  Database `user` table ORM model.
@@ -202,40 +234,50 @@ def build_auth_db(engine: DatabaseEngine | DatabaseEngineAsync) -> None:
202
234
  auth_router = APIRouter()
203
235
 
204
236
 
205
- @auth_router.post('/sessions')
206
- async def create_sessions(
207
- account: str = Bind.i.body,
208
- password: str = Bind.i.body,
209
- account_type: Literal['name', 'email', 'phone'] = Bind.Body('name'),
210
- conn: Bind.Conn = Bind.conn.auth
211
- ) -> dict:
237
+ async def get_user_info(
238
+ conn: Bind.Conn,
239
+ account: str,
240
+ account_type: Literal['name', 'email', 'phone'],
241
+ filter_invalid: bool = True
242
+ ) -> UserInfo | None:
212
243
  """
213
- Create session.
244
+ Get user information.
214
245
 
215
246
  Parameters
216
247
  ----------
217
- account : User account, name or email or phone.
218
- password : User password.
248
+ conn: Asyncronous database connection.
249
+ account : User account.
219
250
  account_type : User account type.
251
+ - `Literal['name']`: User name.
252
+ - `Literal['email']`: User Email.
253
+ - `Literal['phone']`: User phone mumber.
254
+ filter_invalid : Whether filter invalid user.
220
255
 
221
256
  Returns
222
257
  -------
223
- JSON with `token`.
258
+ User information or null.
224
259
  """
225
260
 
226
- # Parameter.
227
- key = ServerConfig.server.api_auth_key
228
- sess_seconds = ServerConfig.server.api_auth_sess_seconds
229
-
230
- # Check.
261
+ # Parameters.
262
+ if filter_invalid:
263
+ sql_where = (
264
+ ' WHERE (\n'
265
+ f' `{account_type}` = :account\n'
266
+ ' AND `is_valid` = 1\n'
267
+ ' )\n'
268
+ )
269
+ else:
270
+ sql_where = ' WHERE `{account_type}` = :account\n'
271
+
272
+ # Get.
231
273
  sql = (
232
274
  'SELECT ANY_VALUE(`create_time`) AS `create_time`,\n'
275
+ ' ANY_VALUE(`phone`) AS `phone`,\n'
233
276
  ' ANY_VALUE(`update_time`) AS `update_time`,\n'
234
277
  ' ANY_VALUE(`user`.`user_id`) AS `user_id`,\n'
235
278
  ' ANY_VALUE(`user`.`name`) AS `user_name`,\n'
236
279
  ' ANY_VALUE(`password`) AS `password`,\n'
237
280
  ' ANY_VALUE(`email`) AS `email`,\n'
238
- ' ANY_VALUE(`phone`) AS `phone`,\n'
239
281
  ' ANY_VALUE(`avatar`) AS `avatar`,\n'
240
282
  " GROUP_CONCAT(DISTINCT `role`.`name` SEPARATOR ';') AS `role_names`,\n"
241
283
  " GROUP_CONCAT(DISTINCT `perm`.`name` SEPARATOR ';') AS `perm_names`,\n"
@@ -243,8 +285,7 @@ async def create_sessions(
243
285
  'FROM (\n'
244
286
  ' SELECT `create_time`, `update_time`, `user_id`, `password`, `name`, `email`, `phone`, `avatar`\n'
245
287
  ' FROM `test`.`user`\n'
246
- f' WHERE `{account_type}` = :account\n'
247
- ' LIMIT 1\n'
288
+ f'{sql_where}'
248
289
  ') as `user`\n'
249
290
  'LEFT JOIN (\n'
250
291
  ' SELECT `user_id`, `role_id`\n'
@@ -262,7 +303,7 @@ async def create_sessions(
262
303
  ') as `role_perm`\n'
263
304
  'ON `role_perm`.`role_id` = `role`.`role_id`\n'
264
305
  'LEFT JOIN (\n'
265
- " SELECT `perm_id`, `name`, CONCAT(`method`, ':', `path`) as `api`\n"
306
+ " SELECT `perm_id`, `name`, `api`\n"
266
307
  ' FROM `test`.`perm`\n'
267
308
  ') AS `perm`\n'
268
309
  'ON `role_perm`.`perm_id` = `perm`.`perm_id`\n'
@@ -273,34 +314,136 @@ async def create_sessions(
273
314
  account=account
274
315
  )
275
316
 
276
- # Check.
317
+ # Extract.
277
318
  if result.empty:
319
+ info = None
320
+ else:
321
+ row: dict[str, Datetime | Any] = result.to_row()
322
+ info: UserInfo = {
323
+ 'create_time': row['create_time'].timestamp(),
324
+ 'udpate_time': row['update_time'].timestamp(),
325
+ 'user_id': row['user_id'],
326
+ 'user_name': row['user_name'],
327
+ 'role_names': row['role_names'].split(';'),
328
+ 'perm_names': row['perm_names'].split(';'),
329
+ 'perm_apis': row['perm_apis'].split(';'),
330
+ 'email': row['email'],
331
+ 'phone': row['phone'],
332
+ 'avatar': row['avatar'],
333
+ 'password': row['password']
334
+ }
335
+
336
+ return info
337
+
338
+
339
+ @auth_router.post('/sessions')
340
+ async def create_sessions(
341
+ account: str = Bind.i.body,
342
+ password: str = Bind.i.body,
343
+ account_type: Literal['name', 'email', 'phone'] = Bind.Body('name'),
344
+ conn: Bind.Conn = Bind.conn.auth,
345
+ server: Bind.Server = Bind.server
346
+ ) -> dict:
347
+ """
348
+ Create session.
349
+
350
+ Parameters
351
+ ----------
352
+ account : User account.
353
+ password : User password.
354
+ account_type : User account type.
355
+ - `Literal['name']`: User name.
356
+ - `Literal['email']`: User Email.
357
+ - `Literal['phone']`: User phone mumber.
358
+
359
+ Returns
360
+ -------
361
+ JSON with `token`.
362
+ """
363
+
364
+ # Parameter.
365
+ key = server.api_auth_key
366
+ sess_seconds = server.api_auth_sess_seconds
367
+
368
+ # User information.
369
+ info = await get_user_info(conn, account, account_type)
370
+
371
+ # Check.
372
+ if info is None:
278
373
  exit_api(401)
279
- json: dict[str, Datetime | Any] = result.to_row()
280
- if not is_hash_bcrypt(password, json['password']):
374
+ password_hash = info.pop('password')
375
+ if not is_hash_bcrypt(password, password_hash):
281
376
  exit_api(401)
282
377
 
283
378
  # JWT.
284
379
  now_timestamp_s = now('timestamp_s')
285
- json['sub'] = json.pop('user_id')
286
- json['iat'] = now_timestamp_s
287
- json['nbf'] = now_timestamp_s
288
- json['exp'] = now_timestamp_s + sess_seconds
289
- json['role_names'] = json['role_names'].split(';')
290
- json['perm_names'] = json['perm_names'].split(';')
291
- perm_apis: list[str] = json['perm_apis'].split(';')
292
- perm_api_dict = {}
293
- for perm_api in perm_apis:
294
- method, path = perm_api.split(':', 1)
295
- paths: list = perm_api_dict.setdefault(method, [])
296
- paths.append(path)
297
- json['perm_apis'] = perm_api_dict
298
- json['create_time'] = json['create_time'].timestamp()
299
- json['update_time'] = json['update_time'].timestamp()
300
- token = encode_jwt(json, key)
301
- data = {'token': token}
302
-
303
- return data
304
-
305
-
306
- def has_auth(request: Request) -> bool: ...
380
+ user_id = info.pop('user_id')
381
+ data: Token = {
382
+ 'sub': str(user_id),
383
+ 'iat': now_timestamp_s,
384
+ 'nbf': now_timestamp_s,
385
+ 'exp': now_timestamp_s + sess_seconds,
386
+ 'user': info
387
+ }
388
+ token = encode_jwt(data, key)
389
+ response = {'token': token}
390
+
391
+ return response
392
+
393
+
394
+ bearer = HTTPBearer(
395
+ scheme_name='RBACBearer',
396
+ description='Global authentication of based on RBAC model and Bearer framework.',
397
+ bearerFormat='JWT standard.',
398
+ auto_error=False
399
+ )
400
+
401
+
402
+ async def depend_auth(
403
+ request: Request,
404
+ server: Bind.Server = Bind.server,
405
+ auth: HTTPAuthorizationCredentials | None = Depends(bearer)
406
+ ) -> None:
407
+ """
408
+ Dependencie function of authentication.
409
+
410
+ Parameters
411
+ ----------
412
+ request : Request.
413
+ server : Server.
414
+ auth : Authentication.
415
+ """
416
+
417
+ # Check.
418
+ api_path = f'{request.method} {request.url.path}'
419
+ if (
420
+ not server.is_started_auth
421
+ or api_path == 'POST /sessions'
422
+ ):
423
+ return
424
+ if auth is None:
425
+ exit_api(401)
426
+
427
+ # Parameter.
428
+ key = server.api_auth_key
429
+ token = auth.credentials
430
+
431
+ # Decode.
432
+ token = decode_jwt(token, key)
433
+ if token is None:
434
+ exit_api(401)
435
+ token: Token
436
+ request.state.token = token
437
+
438
+ # Check.
439
+ perm_apis = token['user']['perm_apis']
440
+ perm_apis = [
441
+ f'^{pattern}$'
442
+ for pattern in perm_apis
443
+ ]
444
+ try:
445
+ result = search_batch(api_path, *perm_apis)
446
+ except PatternError:
447
+ exit_api(403)
448
+ if result is None:
449
+ exit_api(403)
reyserver/rbase.py CHANGED
@@ -9,8 +9,9 @@
9
9
  """
10
10
 
11
11
 
12
- from typing import NoReturn, overload
12
+ from typing import Type, NoReturn, overload
13
13
  from http import HTTPStatus
14
+ from fastapi import FastAPI
14
15
  from fastapi import HTTPException, Request, UploadFile as File
15
16
  from fastapi.params import (
16
17
  Depends,
@@ -24,14 +25,13 @@ from fastapi.params import (
24
25
  )
25
26
  from reydb.rconn import DatabaseConnectionAsync
26
27
  from reydb.rorm import DatabaseORMModel, DatabaseORMSessionAsync
27
- from reykit.rbase import Base, Exit, StaticMeta, ConfigMeta, Singleton, throw
28
+ from reykit.rbase import Base, Exit, StaticMeta, Singleton, throw
28
29
 
29
30
  from . import rserver
30
31
 
31
32
 
32
33
  __all__ = (
33
34
  'ServerBase',
34
- 'ServerConfig',
35
35
  'ServerExit',
36
36
  'ServerExitAPI',
37
37
  'exit_api',
@@ -50,15 +50,6 @@ class ServerBase(Base):
50
50
  """
51
51
 
52
52
 
53
- class ServerConfig(ServerBase, metaclass=ConfigMeta):
54
- """
55
- Config type.
56
- """
57
-
58
- server: 'rserver.Server'
59
- 'Server instance.'
60
-
61
-
62
53
  class ServerExit(ServerBase, Exit):
63
54
  """
64
55
  Server exit type.
@@ -116,13 +107,13 @@ class ServerBindInstanceDatabaseSuper(ServerBase):
116
107
  """
117
108
 
118
109
 
119
- async def depend_func():
110
+ async def depend_func(server: Bind.Server = Bind.server):
120
111
  """
121
112
  Dependencie function of asynchronous database.
122
113
  """
123
114
 
124
115
  # Parameter.
125
- engine = ServerConfig.server.db[name]
116
+ engine = server.db[name]
126
117
 
127
118
  # Context.
128
119
  match self:
@@ -320,6 +311,26 @@ class ServerBindInstance(ServerBase, Singleton):
320
311
  return forms
321
312
 
322
313
 
314
+ async def depend_server(request: Request) -> 'rserver.Server':
315
+ """
316
+ Dependencie function of now Server instance.
317
+
318
+ Parameters
319
+ ----------
320
+ request : Request.
321
+
322
+ Returns
323
+ -------
324
+ Server.
325
+ """
326
+
327
+ # Get.
328
+ app: FastAPI = request.app
329
+ server = app.extra['server']
330
+
331
+ return server
332
+
333
+
323
334
  class ServerBind(ServerBase, metaclass=StaticMeta):
324
335
  """
325
336
  Server API bind parameter type.
@@ -338,6 +349,8 @@ class ServerBind(ServerBase, metaclass=StaticMeta):
338
349
  JSON = DatabaseORMModel
339
350
  Conn = DatabaseConnectionAsync
340
351
  Sess = DatabaseORMSessionAsync
352
+ Server = Type['rserver.Server']
353
+ server = Depends(depend_server)
341
354
  i = ServerBindInstance()
342
355
  conn = ServerBindInstanceDatabaseConnection()
343
356
  sess = ServerBindInstanceDatabaseSession()
reyserver/rfile.py CHANGED
@@ -14,7 +14,7 @@ from fastapi.responses import FileResponse
14
14
  from reydb import rorm, DatabaseEngine, DatabaseEngineAsync
15
15
  from reykit.ros import FileStore, get_md5
16
16
 
17
- from .rbase import ServerConfig, Bind, exit_api
17
+ from .rbase import Bind, exit_api
18
18
 
19
19
 
20
20
  __all__ = (
@@ -212,7 +212,8 @@ async def upload_file(
212
212
  file: Bind.File = Bind.i.forms,
213
213
  name: str = Bind.i.forms_n,
214
214
  note: str = Bind.i.forms_n,
215
- sess: Bind.Sess = Bind.sess.file
215
+ sess: Bind.Sess = Bind.sess.file,
216
+ server: Bind.Server = Bind.server
216
217
  ) -> DatabaseORMTableInfo:
217
218
  """
218
219
  Upload file.
@@ -229,7 +230,7 @@ async def upload_file(
229
230
  """
230
231
 
231
232
  # Handle parameter.
232
- file_store = FileStore(ServerConfig.server.api_file_dir)
233
+ file_store = FileStore(server.api_file_dir)
233
234
  file_bytes = await file.read()
234
235
  file_md5 = get_md5(file_bytes)
235
236
  file_size = len(file_bytes)
reyserver/rserver.py CHANGED
@@ -16,6 +16,7 @@ from contextlib import asynccontextmanager, _AsyncGeneratorContextManager
16
16
  from uvicorn import run as uvicorn_run
17
17
  from starlette.middleware.base import _StreamingResponse
18
18
  from fastapi import FastAPI, Request
19
+ from fastapi.params import Depends
19
20
  from fastapi.staticfiles import StaticFiles
20
21
  from fastapi.middleware.gzip import GZipMiddleware
21
22
  from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
@@ -23,7 +24,7 @@ from reydb import rorm, DatabaseAsync, DatabaseEngineAsync
23
24
  from reykit.rbase import CoroutineFunctionSimple, Singleton, throw
24
25
  from reykit.rrand import randchar
25
26
 
26
- from .rbase import ServerBase, ServerConfig, Bind
27
+ from .rbase import ServerBase, Bind
27
28
  from . import radmin
28
29
 
29
30
 
@@ -68,6 +69,8 @@ class Server(ServerBase, Singleton):
68
69
  debug : Whether use development mode debug server.
69
70
  """
70
71
 
72
+ from .rauth import depend_auth
73
+
71
74
  # Parameter.
72
75
  if type(ssl_cert) != type(ssl_key):
73
76
  throw(AssertionError, ssl_cert, ssl_key)
@@ -76,13 +79,14 @@ class Server(ServerBase, Singleton):
76
79
  elif iscoroutinefunction(depend):
77
80
  depend = (depend,)
78
81
  depend = [
82
+ Bind.Depend(depend_auth)
83
+ ] + [
79
84
  Bind.Depend(task)
80
85
  for task in depend
81
86
  ]
82
87
  lifespan = self.__create_lifespan(before, after, db_warm)
83
88
 
84
89
  # Build.
85
- ServerConfig.server = self
86
90
  self.db = db
87
91
  self.ssl_cert = ssl_cert
88
92
  self.ssl_key = ssl_key
@@ -91,7 +95,8 @@ class Server(ServerBase, Singleton):
91
95
  self.app = FastAPI(
92
96
  dependencies=depend,
93
97
  lifespan=lifespan,
94
- debug=debug
98
+ debug=debug,
99
+ server=self
95
100
  )
96
101
 
97
102
  if public is not None:
@@ -107,6 +112,8 @@ class Server(ServerBase, Singleton):
107
112
  self.__add_default_middleware()
108
113
 
109
114
  # API.
115
+ self.is_started_auth: bool = False
116
+ 'Whether start authentication.'
110
117
  self.api_auth_key: str
111
118
  'Authentication API JWT encryption key.'
112
119
  self.api_auth_sess_seconds: int
@@ -185,7 +192,7 @@ class Server(ServerBase, Singleton):
185
192
 
186
193
  # Add.
187
194
  @self.wrap_middleware
188
- async def foo(
195
+ async def middleware(
189
196
  request: Request,
190
197
  call_next: Callable[[Request], Coroutine[None, None, _StreamingResponse]]
191
198
  ) -> _StreamingResponse:
@@ -262,6 +269,16 @@ class Server(ServerBase, Singleton):
262
269
  setattr(self.app, key, value)
263
270
 
264
271
 
272
+ def add_api_base(self) -> None:
273
+ """
274
+ Add base API.
275
+ """
276
+ from fastapi import Request
277
+ @self.app.get('/test')
278
+ async def test(request: Request) -> str:
279
+ return 'test'
280
+
281
+
265
282
  def add_api_admin(self) -> None:
266
283
  """
267
284
  Add admin API.
@@ -315,13 +332,14 @@ class Server(ServerBase, Singleton):
315
332
  """
316
333
 
317
334
  from .rauth import (
318
- build_auth_db,
319
- auth_router,
320
335
  DatabaseORMTableUser,
321
336
  DatabaseORMTableRole,
322
337
  DatabaseORMTablePerm,
323
338
  DatabaseORMTableUserRole,
324
- DatabaseORMTableRolePerm
339
+ DatabaseORMTableRolePerm,
340
+ build_auth_db,
341
+ auth_router,
342
+ depend_auth
325
343
  )
326
344
 
327
345
  # Parameter.
@@ -338,6 +356,7 @@ class Server(ServerBase, Singleton):
338
356
  self.api_auth_key = key
339
357
  self.api_auth_sess_seconds = sess_seconds
340
358
  self.app.include_router(auth_router, tags=['auth'])
359
+ self.is_started_auth = True
341
360
 
342
361
  ## Admin.
343
362
  self.add_admin_model(DatabaseORMTableUser, engine, category='auth', name='User', column_list=['user_id', 'name'])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reyserver
3
- Version: 1.1.58
3
+ Version: 1.1.59
4
4
  Summary: Backend server method set.
5
5
  Project-URL: homepage, https://github.com/reyxbo/reyserver/
6
6
  Author-email: Rey <reyxbo@163.com>
@@ -0,0 +1,12 @@
1
+ reyserver/__init__.py,sha256=Oq-lOcQInzhgKt1_4OB2jNx0OO2d9qZg9iZqUx6YRr8,398
2
+ reyserver/radmin.py,sha256=Hwy8QsiQOyK2YP7abcS22IbRKB7sgcDGPHQ2-mHtf-8,3269
3
+ reyserver/rall.py,sha256=MI1NnqUpq22CK9w3XPPBS0K8lNuf0BnbI0pn4MGMx38,293
4
+ reyserver/rauth.py,sha256=irnwf1_OSOjB9fB7VBWY5RvpetPX2ydivMt_teXmDVs,15079
5
+ reyserver/rbase.py,sha256=kzSbo6yoPlWquR6XiseTKi5hEbllaG3qVmmwnnKKac0,6898
6
+ reyserver/rclient.py,sha256=Ffm66YuWupzEtQ6RqEm2KCc6jSF8JeMB7Xq6pzRFZ6M,5073
7
+ reyserver/rfile.py,sha256=0wS-ohsAOGcSCg07U66wpqJlxnCLWN6gkEQJK4qavWQ,8877
8
+ reyserver/rserver.py,sha256=mjc-SeE2RZZwB5Z1nUSMo_VMign3VcftIK1Kv7XIne8,11484
9
+ reyserver-1.1.59.dist-info/METADATA,sha256=oJNACNqqErBtx9jcDkkGRQoTyDaFT-T5tnfbEkVm-6E,1666
10
+ reyserver-1.1.59.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
11
+ reyserver-1.1.59.dist-info/licenses/LICENSE,sha256=UYLPqp7BvPiH8yEZduJqmmyEl6hlM3lKrFIefiD4rvk,1059
12
+ reyserver-1.1.59.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- reyserver/__init__.py,sha256=Oq-lOcQInzhgKt1_4OB2jNx0OO2d9qZg9iZqUx6YRr8,398
2
- reyserver/radmin.py,sha256=Hwy8QsiQOyK2YP7abcS22IbRKB7sgcDGPHQ2-mHtf-8,3269
3
- reyserver/rall.py,sha256=MI1NnqUpq22CK9w3XPPBS0K8lNuf0BnbI0pn4MGMx38,293
4
- reyserver/rauth.py,sha256=5zQwi7eGjhHD2BHGQDQclLCn3HHg646Q4Cp2Bt6i3uc,11701
5
- reyserver/rbase.py,sha256=_bP_suBPF7dLd4RNKn96-X4ZAEx3vAjMS6vnheCokU4,6617
6
- reyserver/rclient.py,sha256=Ffm66YuWupzEtQ6RqEm2KCc6jSF8JeMB7Xq6pzRFZ6M,5073
7
- reyserver/rfile.py,sha256=CH2uJbBNmBH9ISDirn1LWL5wsjGW5xjB9ZIrdc5UzL0,8864
8
- reyserver/rserver.py,sha256=9-bhgVn_XZ6bvBl4GLdGkUs4UR1TSG08WC3F6ETQo7M,10986
9
- reyserver-1.1.58.dist-info/METADATA,sha256=l98QLYqG9iBeisPA5SgdMpu-aEZll2tWYnG4u7lCgfM,1666
10
- reyserver-1.1.58.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
11
- reyserver-1.1.58.dist-info/licenses/LICENSE,sha256=UYLPqp7BvPiH8yEZduJqmmyEl6hlM3lKrFIefiD4rvk,1059
12
- reyserver-1.1.58.dist-info/RECORD,,