reyserver 1.1.60__py3-none-any.whl → 1.1.62__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/__init__.py +0 -1
- reyserver/rall.py +0 -1
- reyserver/rauth.py +120 -111
- reyserver/rbase.py +58 -25
- reyserver/rclient.py +9 -10
- reyserver/rfile.py +7 -7
- reyserver/rserver.py +46 -89
- {reyserver-1.1.60.dist-info → reyserver-1.1.62.dist-info}/METADATA +1 -1
- reyserver-1.1.62.dist-info/RECORD +11 -0
- reyserver/radmin.py +0 -171
- reyserver-1.1.60.dist-info/RECORD +0 -12
- {reyserver-1.1.60.dist-info → reyserver-1.1.62.dist-info}/WHEEL +0 -0
- {reyserver-1.1.60.dist-info → reyserver-1.1.62.dist-info}/licenses/LICENSE +0 -0
reyserver/__init__.py
CHANGED
reyserver/rall.py
CHANGED
reyserver/rauth.py
CHANGED
|
@@ -11,10 +11,8 @@
|
|
|
11
11
|
|
|
12
12
|
from typing import Any, TypedDict, NotRequired, Literal
|
|
13
13
|
from datetime import datetime as Datetime
|
|
14
|
-
from re import PatternError
|
|
15
14
|
from fastapi import APIRouter, Request
|
|
16
|
-
from fastapi.
|
|
17
|
-
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
15
|
+
from fastapi.security import OAuth2PasswordBearer
|
|
18
16
|
from reydb import rorm, DatabaseEngine, DatabaseEngineAsync
|
|
19
17
|
from reykit.rdata import encode_jwt, decode_jwt, is_hash_bcrypt
|
|
20
18
|
from reykit.rre import search_batch
|
|
@@ -29,35 +27,43 @@ __all__ = (
|
|
|
29
27
|
'DatabaseORMTablePerm',
|
|
30
28
|
'DatabaseORMTableUserRole',
|
|
31
29
|
'DatabaseORMTableRolePerm',
|
|
32
|
-
'
|
|
33
|
-
'
|
|
30
|
+
'build_db_auth',
|
|
31
|
+
'router_auth'
|
|
34
32
|
)
|
|
35
33
|
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
'
|
|
35
|
+
UserData = TypedDict(
|
|
36
|
+
'UserData',
|
|
37
|
+
{
|
|
38
|
+
'create_time': float,
|
|
39
|
+
'udpate_time': float,
|
|
40
|
+
'user_id': int,
|
|
41
|
+
'user_name': str,
|
|
42
|
+
'role_names': list[str],
|
|
43
|
+
'perm_names': list[str],
|
|
44
|
+
'perm_apis': list[str],
|
|
45
|
+
'email': str | None,
|
|
46
|
+
'phone': str | None,
|
|
47
|
+
'avatar': int | None,
|
|
48
|
+
'password': NotRequired[str]
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
TokenData = TypedDict(
|
|
52
|
+
'TokenData',
|
|
55
53
|
{
|
|
56
54
|
'sub': int,
|
|
57
55
|
'iat': int,
|
|
58
56
|
'nbf': int,
|
|
59
57
|
'exp': int,
|
|
60
|
-
'user':
|
|
58
|
+
'user': UserData
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
Token = str
|
|
62
|
+
JSONToken = TypedDict(
|
|
63
|
+
'JSONToken',
|
|
64
|
+
{
|
|
65
|
+
'access_token': Token,
|
|
66
|
+
'token_type': Literal['Bearer']
|
|
61
67
|
}
|
|
62
68
|
)
|
|
63
69
|
|
|
@@ -138,7 +144,7 @@ class DatabaseORMTableRolePerm(rorm.Table):
|
|
|
138
144
|
perm_id: int = rorm.Field(rorm.types_mysql.SMALLINT(unsigned=True), key=True, comment='Permission ID.')
|
|
139
145
|
|
|
140
146
|
|
|
141
|
-
def
|
|
147
|
+
def build_db_auth(engine: DatabaseEngine | DatabaseEngineAsync) -> None:
|
|
142
148
|
"""
|
|
143
149
|
Check and build `auth` database tables.
|
|
144
150
|
|
|
@@ -231,17 +237,80 @@ def build_auth_db(engine: DatabaseEngine | DatabaseEngineAsync) -> None:
|
|
|
231
237
|
engine.sync_engine.build.build(tables=tables, views_stats=views_stats, skip=True)
|
|
232
238
|
|
|
233
239
|
|
|
234
|
-
|
|
240
|
+
bearer = OAuth2PasswordBearer(
|
|
241
|
+
tokenUrl='/token',
|
|
242
|
+
scheme_name='OAuth2Password',
|
|
243
|
+
description='Authentication of OAuth2 password model.',
|
|
244
|
+
auto_error=False
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
async def depend_token(
|
|
249
|
+
request: Request,
|
|
250
|
+
server: Bind.Server = Bind.server,
|
|
251
|
+
token: Token | None = Bind.Depend(bearer)
|
|
252
|
+
) -> TokenData:
|
|
253
|
+
"""
|
|
254
|
+
Dependencie function of authentication token.
|
|
255
|
+
If the verification fails, then response status code is 401 or 403.
|
|
256
|
+
|
|
257
|
+
Parameters
|
|
258
|
+
----------
|
|
259
|
+
request : Request.
|
|
260
|
+
server : Server.
|
|
261
|
+
token : Authentication token.
|
|
262
|
+
|
|
263
|
+
Returns
|
|
264
|
+
-------
|
|
265
|
+
Token data.
|
|
266
|
+
"""
|
|
267
|
+
|
|
268
|
+
# Check.
|
|
269
|
+
if not server.is_started_auth:
|
|
270
|
+
return
|
|
271
|
+
if bearer is None:
|
|
272
|
+
exit_api(401)
|
|
273
|
+
|
|
274
|
+
# Parameter.
|
|
275
|
+
key = server.api_auth_key
|
|
276
|
+
api_path = f'{request.method} {request.url.path}'
|
|
277
|
+
|
|
278
|
+
# Cache.
|
|
279
|
+
token_data: UserData | None = getattr(request.state, 'token_data', None)
|
|
280
|
+
|
|
281
|
+
# Decode.
|
|
282
|
+
if token_data is None:
|
|
283
|
+
token_data: TokenData | None = decode_jwt(token, key)
|
|
284
|
+
if token_data is None:
|
|
285
|
+
exit_api(401)
|
|
286
|
+
request.state.token_data = token_data
|
|
287
|
+
|
|
288
|
+
# Authentication.
|
|
289
|
+
perm_apis = token_data['user']['perm_apis']
|
|
290
|
+
perm_apis = [
|
|
291
|
+
f'^{pattern}$'
|
|
292
|
+
for pattern in perm_apis
|
|
293
|
+
]
|
|
294
|
+
result = search_batch(api_path, *perm_apis)
|
|
295
|
+
if result is None:
|
|
296
|
+
exit_api(403)
|
|
297
|
+
|
|
298
|
+
return token_data
|
|
299
|
+
|
|
235
300
|
|
|
301
|
+
Bind.token = Bind.Depend(depend_token)
|
|
236
302
|
|
|
237
|
-
|
|
303
|
+
router_auth = APIRouter()
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
async def get_user_data(
|
|
238
307
|
conn: Bind.Conn,
|
|
239
308
|
account: str,
|
|
240
|
-
account_type: Literal['name', 'email', 'phone'],
|
|
309
|
+
account_type: Literal['user_id', 'name', 'email', 'phone'] = 'name',
|
|
241
310
|
filter_invalid: bool = True
|
|
242
|
-
) ->
|
|
311
|
+
) -> UserData | None:
|
|
243
312
|
"""
|
|
244
|
-
Get user
|
|
313
|
+
Get user data.
|
|
245
314
|
|
|
246
315
|
Parameters
|
|
247
316
|
----------
|
|
@@ -255,7 +324,7 @@ async def get_user_info(
|
|
|
255
324
|
|
|
256
325
|
Returns
|
|
257
326
|
-------
|
|
258
|
-
User
|
|
327
|
+
User data or null.
|
|
259
328
|
"""
|
|
260
329
|
|
|
261
330
|
# Parameters.
|
|
@@ -286,6 +355,7 @@ async def get_user_info(
|
|
|
286
355
|
' SELECT `create_time`, `update_time`, `user_id`, `password`, `name`, `email`, `phone`, `avatar`\n'
|
|
287
356
|
' FROM `test`.`user`\n'
|
|
288
357
|
f'{sql_where}'
|
|
358
|
+
' LIMIT 1\n'
|
|
289
359
|
') as `user`\n'
|
|
290
360
|
'LEFT JOIN (\n'
|
|
291
361
|
' SELECT `user_id`, `role_id`\n'
|
|
@@ -319,7 +389,7 @@ async def get_user_info(
|
|
|
319
389
|
info = None
|
|
320
390
|
else:
|
|
321
391
|
row: dict[str, Datetime | Any] = result.to_row()
|
|
322
|
-
info:
|
|
392
|
+
info: UserData = {
|
|
323
393
|
'create_time': row['create_time'].timestamp(),
|
|
324
394
|
'udpate_time': row['update_time'].timestamp(),
|
|
325
395
|
'user_id': row['user_id'],
|
|
@@ -336,25 +406,20 @@ async def get_user_info(
|
|
|
336
406
|
return info
|
|
337
407
|
|
|
338
408
|
|
|
339
|
-
@
|
|
409
|
+
@router_auth.post('/token')
|
|
340
410
|
async def create_sessions(
|
|
341
|
-
|
|
342
|
-
password: str = Bind.i.
|
|
343
|
-
account_type: Literal['name', 'email', 'phone'] = Bind.Body('name'),
|
|
411
|
+
username: str = Bind.i.form,
|
|
412
|
+
password: str = Bind.i.form,
|
|
344
413
|
conn: Bind.Conn = Bind.conn.auth,
|
|
345
414
|
server: Bind.Server = Bind.server
|
|
346
|
-
) ->
|
|
415
|
+
) -> JSONToken:
|
|
347
416
|
"""
|
|
348
|
-
Create
|
|
417
|
+
Create token.
|
|
349
418
|
|
|
350
419
|
Parameters
|
|
351
420
|
----------
|
|
352
|
-
|
|
421
|
+
username : User name.
|
|
353
422
|
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
423
|
|
|
359
424
|
Returns
|
|
360
425
|
-------
|
|
@@ -365,86 +430,30 @@ async def create_sessions(
|
|
|
365
430
|
key = server.api_auth_key
|
|
366
431
|
sess_seconds = server.api_auth_sess_seconds
|
|
367
432
|
|
|
368
|
-
# User
|
|
369
|
-
|
|
433
|
+
# User data.
|
|
434
|
+
user_data = await get_user_data(conn, username)
|
|
370
435
|
|
|
371
436
|
# Check.
|
|
372
|
-
if
|
|
437
|
+
if user_data is None:
|
|
373
438
|
exit_api(401)
|
|
374
|
-
password_hash =
|
|
439
|
+
password_hash = user_data.pop('password')
|
|
375
440
|
if not is_hash_bcrypt(password, password_hash):
|
|
376
441
|
exit_api(401)
|
|
377
442
|
|
|
378
|
-
#
|
|
443
|
+
# Response.
|
|
379
444
|
now_timestamp_s = now('timestamp_s')
|
|
380
|
-
user_id =
|
|
381
|
-
data:
|
|
445
|
+
user_id = user_data.pop('user_id')
|
|
446
|
+
data: TokenData = {
|
|
382
447
|
'sub': str(user_id),
|
|
383
448
|
'iat': now_timestamp_s,
|
|
384
449
|
'nbf': now_timestamp_s,
|
|
385
450
|
'exp': now_timestamp_s + sess_seconds,
|
|
386
|
-
'user':
|
|
451
|
+
'user': user_data
|
|
387
452
|
}
|
|
388
453
|
token = encode_jwt(data, key)
|
|
389
|
-
response = {
|
|
454
|
+
response = {
|
|
455
|
+
'access_token': token,
|
|
456
|
+
'token_type': 'Bearer'
|
|
457
|
+
}
|
|
390
458
|
|
|
391
459
|
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
|
-
print(11111111111111111)
|
|
419
|
-
api_path = f'{request.method} {request.url.path}'
|
|
420
|
-
if (
|
|
421
|
-
not server.is_started_auth
|
|
422
|
-
or api_path == 'POST /sessions'
|
|
423
|
-
):
|
|
424
|
-
return
|
|
425
|
-
if auth is None:
|
|
426
|
-
exit_api(401)
|
|
427
|
-
|
|
428
|
-
# Parameter.
|
|
429
|
-
key = server.api_auth_key
|
|
430
|
-
token = auth.credentials
|
|
431
|
-
|
|
432
|
-
# Decode.
|
|
433
|
-
json = decode_jwt(token, key)
|
|
434
|
-
if json is None:
|
|
435
|
-
exit_api(401)
|
|
436
|
-
json: Token
|
|
437
|
-
request.state.user = json['user']
|
|
438
|
-
|
|
439
|
-
# Check.
|
|
440
|
-
perm_apis = json['user']['perm_apis']
|
|
441
|
-
perm_apis = [
|
|
442
|
-
f'^{pattern}$'
|
|
443
|
-
for pattern in perm_apis
|
|
444
|
-
]
|
|
445
|
-
try:
|
|
446
|
-
result = search_batch(api_path, *perm_apis)
|
|
447
|
-
except PatternError:
|
|
448
|
-
exit_api(403)
|
|
449
|
-
if result is None:
|
|
450
|
-
exit_api(403)
|
reyserver/rbase.py
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
from typing import Type, NoReturn, overload
|
|
12
|
+
from typing import Literal, Type, NoReturn, overload
|
|
13
13
|
from http import HTTPStatus
|
|
14
|
-
from fastapi import
|
|
14
|
+
from fastapi import APIRouter
|
|
15
15
|
from fastapi import HTTPException, Request, UploadFile as File
|
|
16
16
|
from fastapi.params import (
|
|
17
17
|
Depends,
|
|
@@ -24,7 +24,7 @@ from fastapi.params import (
|
|
|
24
24
|
File as Forms
|
|
25
25
|
)
|
|
26
26
|
from reydb.rconn import DatabaseConnectionAsync
|
|
27
|
-
from reydb.rorm import
|
|
27
|
+
from reydb.rorm import DatabaseORMSessionAsync
|
|
28
28
|
from reykit.rbase import Base, Exit, StaticMeta, Singleton, throw
|
|
29
29
|
|
|
30
30
|
from . import rserver
|
|
@@ -311,49 +311,82 @@ class ServerBindInstance(ServerBase, Singleton):
|
|
|
311
311
|
return forms
|
|
312
312
|
|
|
313
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
|
-
|
|
334
314
|
class ServerBind(ServerBase, metaclass=StaticMeta):
|
|
335
315
|
"""
|
|
336
316
|
Server API bind parameter type.
|
|
337
317
|
"""
|
|
338
318
|
|
|
339
319
|
Request = Request
|
|
320
|
+
'Reqeust instance dependency type.'
|
|
340
321
|
Path = Path
|
|
322
|
+
'URL source path dependency type.'
|
|
341
323
|
Query = Query
|
|
324
|
+
'URL query parameter dependency type.'
|
|
342
325
|
Header = Header
|
|
326
|
+
'Request header parameter dependency type.'
|
|
343
327
|
Cookie = Cookie
|
|
328
|
+
'Request header cookie parameter dependency type.'
|
|
344
329
|
Body = Body
|
|
330
|
+
'Request body JSON parameter dependency type.'
|
|
345
331
|
Form = Form
|
|
332
|
+
'Request body form parameter dependency type.'
|
|
346
333
|
Forms = Forms
|
|
334
|
+
'Request body multiple forms parameter dependency type.'
|
|
347
335
|
File = File
|
|
336
|
+
'Verify file type.'
|
|
348
337
|
Depend = Depends
|
|
349
|
-
|
|
338
|
+
'Dependency type.'
|
|
350
339
|
Conn = DatabaseConnectionAsync
|
|
351
340
|
Sess = DatabaseORMSessionAsync
|
|
352
341
|
Server = Type['rserver.Server']
|
|
353
|
-
|
|
342
|
+
'Server type.'
|
|
343
|
+
server: Depend
|
|
344
|
+
'Server instance dependency type.'
|
|
354
345
|
i = ServerBindInstance()
|
|
346
|
+
'Server API bind parameter build instance.'
|
|
355
347
|
conn = ServerBindInstanceDatabaseConnection()
|
|
348
|
+
'Server API bind parameter asynchronous database connection.'
|
|
356
349
|
sess = ServerBindInstanceDatabaseSession()
|
|
350
|
+
'Server API bind parameter asynchronous database session.'
|
|
351
|
+
token: Depend
|
|
352
|
+
'Server authentication token dependency type.'
|
|
357
353
|
|
|
358
354
|
|
|
359
355
|
Bind = ServerBind
|
|
356
|
+
|
|
357
|
+
router_base = APIRouter()
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
@router_base.get('/test')
|
|
361
|
+
def test() -> Literal['test']:
|
|
362
|
+
"""
|
|
363
|
+
Test.
|
|
364
|
+
|
|
365
|
+
Returns
|
|
366
|
+
-------
|
|
367
|
+
Text `test`.
|
|
368
|
+
"""
|
|
369
|
+
|
|
370
|
+
# Resposne.
|
|
371
|
+
response = 'test'
|
|
372
|
+
|
|
373
|
+
return response
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
@router_base.post('/test/echo')
|
|
377
|
+
def echo(data: dict = Bind.i.body) -> dict:
|
|
378
|
+
"""
|
|
379
|
+
Echo test.
|
|
380
|
+
|
|
381
|
+
Paremeters
|
|
382
|
+
----------
|
|
383
|
+
data : Echo data.
|
|
384
|
+
|
|
385
|
+
Returns
|
|
386
|
+
-------
|
|
387
|
+
Echo data.
|
|
388
|
+
"""
|
|
389
|
+
|
|
390
|
+
# Resposne.
|
|
391
|
+
|
|
392
|
+
return data
|
reyserver/rclient.py
CHANGED
|
@@ -53,22 +53,21 @@ class ServerClient(ServerBase):
|
|
|
53
53
|
self.username = username
|
|
54
54
|
self.password = password
|
|
55
55
|
self.url = url
|
|
56
|
-
self.token = self.
|
|
56
|
+
self.token = self.get_token(username, password)
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
def
|
|
59
|
+
def get_token(
|
|
60
60
|
self,
|
|
61
61
|
username: str,
|
|
62
62
|
password: str
|
|
63
63
|
) -> str:
|
|
64
64
|
"""
|
|
65
|
-
|
|
65
|
+
Get token.
|
|
66
66
|
|
|
67
67
|
Parameters
|
|
68
68
|
----------
|
|
69
|
-
|
|
69
|
+
username : User name.
|
|
70
70
|
password : User password.
|
|
71
|
-
account_type : User account type.
|
|
72
71
|
|
|
73
72
|
Returns
|
|
74
73
|
-------
|
|
@@ -76,16 +75,16 @@ class ServerClient(ServerBase):
|
|
|
76
75
|
"""
|
|
77
76
|
|
|
78
77
|
# Parameter.
|
|
79
|
-
url = join_url(self.url, '
|
|
80
|
-
|
|
81
|
-
'
|
|
78
|
+
url = join_url(self.url, 'token')
|
|
79
|
+
data = {
|
|
80
|
+
'username': username,
|
|
82
81
|
'password': password
|
|
83
82
|
}
|
|
84
83
|
|
|
85
84
|
# Request.
|
|
86
|
-
response = request(url,
|
|
85
|
+
response = request(url, data=data, check=True)
|
|
87
86
|
response_dict = response.json()
|
|
88
|
-
token = response_dict['
|
|
87
|
+
token = response_dict['access_token']
|
|
89
88
|
|
|
90
89
|
return token
|
|
91
90
|
|
reyserver/rfile.py
CHANGED
|
@@ -20,8 +20,8 @@ from .rbase import Bind, exit_api
|
|
|
20
20
|
__all__ = (
|
|
21
21
|
'DatabaseORMTableInfo',
|
|
22
22
|
'DatabaseORMTableData',
|
|
23
|
-
'
|
|
24
|
-
'
|
|
23
|
+
'build_db_file',
|
|
24
|
+
'router_file'
|
|
25
25
|
)
|
|
26
26
|
|
|
27
27
|
|
|
@@ -51,7 +51,7 @@ class DatabaseORMTableData(rorm.Table):
|
|
|
51
51
|
path: str = rorm.Field(rorm.types.VARCHAR(4095), not_null=True, comment='File disk storage path.')
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
def
|
|
54
|
+
def build_db_file(engine: DatabaseEngine | DatabaseEngineAsync) -> None:
|
|
55
55
|
"""
|
|
56
56
|
Check and build `file` database tables.
|
|
57
57
|
|
|
@@ -177,10 +177,10 @@ def build_file_db(engine: DatabaseEngine | DatabaseEngineAsync) -> None:
|
|
|
177
177
|
engine.sync_engine.build.build(tables=tables, views=views, views_stats=views_stats, skip=True)
|
|
178
178
|
|
|
179
179
|
|
|
180
|
-
|
|
180
|
+
router_file = APIRouter()
|
|
181
181
|
|
|
182
182
|
|
|
183
|
-
@
|
|
183
|
+
@router_file.get('/files/{file_id}')
|
|
184
184
|
async def get_file_info(
|
|
185
185
|
file_id: int = Bind.i.path,
|
|
186
186
|
sess: Bind.Sess = Bind.sess.file
|
|
@@ -207,7 +207,7 @@ async def get_file_info(
|
|
|
207
207
|
return table_info
|
|
208
208
|
|
|
209
209
|
|
|
210
|
-
@
|
|
210
|
+
@router_file.post('/files')
|
|
211
211
|
async def upload_file(
|
|
212
212
|
file: Bind.File = Bind.i.forms,
|
|
213
213
|
name: str = Bind.i.forms_n,
|
|
@@ -262,7 +262,7 @@ async def upload_file(
|
|
|
262
262
|
return table_info
|
|
263
263
|
|
|
264
264
|
|
|
265
|
-
@
|
|
265
|
+
@router_file.get('/files/{file_id}/download')
|
|
266
266
|
async def download_file(
|
|
267
267
|
file_id: int = Bind.i.path,
|
|
268
268
|
conn: Bind.Conn = Bind.conn.file
|
reyserver/rserver.py
CHANGED
|
@@ -9,23 +9,21 @@
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
from typing import
|
|
12
|
+
from typing import Literal
|
|
13
13
|
from collections.abc import Sequence, Callable, Coroutine
|
|
14
14
|
from inspect import iscoroutinefunction
|
|
15
15
|
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
|
|
20
19
|
from fastapi.staticfiles import StaticFiles
|
|
21
20
|
from fastapi.middleware.gzip import GZipMiddleware
|
|
22
21
|
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
|
|
23
|
-
from reydb import
|
|
22
|
+
from reydb import DatabaseAsync
|
|
24
23
|
from reykit.rbase import CoroutineFunctionSimple, Singleton, throw
|
|
25
24
|
from reykit.rrand import randchar
|
|
26
25
|
|
|
27
26
|
from .rbase import ServerBase, Bind
|
|
28
|
-
from . import radmin
|
|
29
27
|
|
|
30
28
|
|
|
31
29
|
__all__ = (
|
|
@@ -69,8 +67,6 @@ class Server(ServerBase, Singleton):
|
|
|
69
67
|
debug : Whether use development mode debug server.
|
|
70
68
|
"""
|
|
71
69
|
|
|
72
|
-
from .rauth import depend_auth
|
|
73
|
-
|
|
74
70
|
# Parameter.
|
|
75
71
|
if type(ssl_cert) != type(ssl_key):
|
|
76
72
|
throw(AssertionError, ssl_cert, ssl_key)
|
|
@@ -79,8 +75,6 @@ class Server(ServerBase, Singleton):
|
|
|
79
75
|
elif iscoroutinefunction(depend):
|
|
80
76
|
depend = (depend,)
|
|
81
77
|
depend = [
|
|
82
|
-
Bind.Depend(depend_auth)
|
|
83
|
-
] + [
|
|
84
78
|
Bind.Depend(task)
|
|
85
79
|
for task in depend
|
|
86
80
|
]
|
|
@@ -90,8 +84,6 @@ class Server(ServerBase, Singleton):
|
|
|
90
84
|
self.db = db
|
|
91
85
|
self.ssl_cert = ssl_cert
|
|
92
86
|
self.ssl_key = ssl_key
|
|
93
|
-
|
|
94
|
-
## App.
|
|
95
87
|
self.app = FastAPI(
|
|
96
88
|
dependencies=depend,
|
|
97
89
|
lifespan=lifespan,
|
|
@@ -99,17 +91,18 @@ class Server(ServerBase, Singleton):
|
|
|
99
91
|
server=self
|
|
100
92
|
)
|
|
101
93
|
|
|
94
|
+
# Public file.
|
|
102
95
|
if public is not None:
|
|
103
96
|
subapp = StaticFiles(directory=public, html=True)
|
|
104
97
|
self.app.mount('/', subapp)
|
|
105
|
-
self.wrap_middleware = self.app.middleware('http')
|
|
106
|
-
'Decorator, add middleware to APP.'
|
|
107
98
|
|
|
108
99
|
# Middleware
|
|
100
|
+
self.wrap_middleware = self.app.middleware('http')
|
|
101
|
+
'Decorator, add middleware to APP.'
|
|
109
102
|
self.app.add_middleware(GZipMiddleware)
|
|
110
103
|
if not debug:
|
|
111
104
|
self.app.add_middleware(HTTPSRedirectMiddleware)
|
|
112
|
-
self.
|
|
105
|
+
self.__add_base_middleware()
|
|
113
106
|
|
|
114
107
|
# API.
|
|
115
108
|
self.is_started_auth: bool = False
|
|
@@ -185,19 +178,19 @@ class Server(ServerBase, Singleton):
|
|
|
185
178
|
return lifespan
|
|
186
179
|
|
|
187
180
|
|
|
188
|
-
def
|
|
181
|
+
def __add_base_middleware(self) -> None:
|
|
189
182
|
"""
|
|
190
|
-
Add
|
|
183
|
+
Add base middleware.
|
|
191
184
|
"""
|
|
192
185
|
|
|
193
186
|
# Add.
|
|
194
187
|
@self.wrap_middleware
|
|
195
|
-
async def
|
|
188
|
+
async def base_middleware(
|
|
196
189
|
request: Request,
|
|
197
190
|
call_next: Callable[[Request], Coroutine[None, None, _StreamingResponse]]
|
|
198
191
|
) -> _StreamingResponse:
|
|
199
192
|
"""
|
|
200
|
-
|
|
193
|
+
Base middleware.
|
|
201
194
|
|
|
202
195
|
Parameters
|
|
203
196
|
----------
|
|
@@ -217,6 +210,8 @@ class Server(ServerBase, Singleton):
|
|
|
217
210
|
and request.method == 'POST'
|
|
218
211
|
):
|
|
219
212
|
response.status_code = 201
|
|
213
|
+
elif response.status_code == 401:
|
|
214
|
+
response.headers.setdefault('WWW-Authenticate', 'Bearer')
|
|
220
215
|
|
|
221
216
|
return response
|
|
222
217
|
|
|
@@ -234,6 +229,9 @@ class Server(ServerBase, Singleton):
|
|
|
234
229
|
)
|
|
235
230
|
|
|
236
231
|
|
|
232
|
+
__call__ = run
|
|
233
|
+
|
|
234
|
+
|
|
237
235
|
def set_doc(
|
|
238
236
|
self,
|
|
239
237
|
version: str | None = None,
|
|
@@ -273,50 +271,11 @@ class Server(ServerBase, Singleton):
|
|
|
273
271
|
"""
|
|
274
272
|
Add base API.
|
|
275
273
|
"""
|
|
276
|
-
from fastapi import Request
|
|
277
|
-
@self.app.get('/test')
|
|
278
|
-
async def test(request: Request) -> str:
|
|
279
|
-
return 'test'
|
|
280
|
-
|
|
281
274
|
|
|
282
|
-
|
|
283
|
-
"""
|
|
284
|
-
Add admin API.
|
|
285
|
-
"""
|
|
275
|
+
from .rbase import router_base
|
|
286
276
|
|
|
287
277
|
# Add.
|
|
288
|
-
self.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
def add_admin_model(
|
|
292
|
-
self,
|
|
293
|
-
model: Type['radmin.ServerAdminModel'] | type[rorm.Model],
|
|
294
|
-
engine: DatabaseEngineAsync | str = None,
|
|
295
|
-
label: str | None = None,
|
|
296
|
-
name: str | None = None,
|
|
297
|
-
column: str | Sequence[str] | None = None,
|
|
298
|
-
**attrs: Any
|
|
299
|
-
) -> None:
|
|
300
|
-
"""
|
|
301
|
-
Add admin model type.
|
|
302
|
-
|
|
303
|
-
Parameters
|
|
304
|
-
----------
|
|
305
|
-
model : Model type.
|
|
306
|
-
- `type[rorm.Model]`: Define as `ServerAdminModel`.
|
|
307
|
-
engine : Database engine or name.
|
|
308
|
-
label : Admin model class label.
|
|
309
|
-
name : Admin model name.
|
|
310
|
-
column : Admin model display column names.
|
|
311
|
-
attrs : Other admin model attributes.
|
|
312
|
-
"""
|
|
313
|
-
|
|
314
|
-
# Check.
|
|
315
|
-
if not hasattr(self, 'admin'):
|
|
316
|
-
return
|
|
317
|
-
|
|
318
|
-
# Add.
|
|
319
|
-
self.admin.add_model(model, engine, label, name, column, **attrs)
|
|
278
|
+
self.app.include_router(router_base, tags=['test'])
|
|
320
279
|
|
|
321
280
|
|
|
322
281
|
def add_api_auth(self, key: str | None = None, sess_seconds: int = 28800) -> None:
|
|
@@ -331,16 +290,7 @@ class Server(ServerBase, Singleton):
|
|
|
331
290
|
sess_seconds : Session valid seconds.
|
|
332
291
|
"""
|
|
333
292
|
|
|
334
|
-
from .rauth import
|
|
335
|
-
DatabaseORMTableUser,
|
|
336
|
-
DatabaseORMTableRole,
|
|
337
|
-
DatabaseORMTablePerm,
|
|
338
|
-
DatabaseORMTableUserRole,
|
|
339
|
-
DatabaseORMTableRolePerm,
|
|
340
|
-
build_auth_db,
|
|
341
|
-
auth_router,
|
|
342
|
-
depend_auth
|
|
343
|
-
)
|
|
293
|
+
from .rauth import build_db_auth, router_auth
|
|
344
294
|
|
|
345
295
|
# Parameter.
|
|
346
296
|
if key is None:
|
|
@@ -350,21 +300,14 @@ class Server(ServerBase, Singleton):
|
|
|
350
300
|
if 'auth' not in self.db:
|
|
351
301
|
throw(AssertionError, self.db)
|
|
352
302
|
engine = self.db.auth
|
|
353
|
-
|
|
303
|
+
build_db_auth(engine)
|
|
354
304
|
|
|
355
305
|
# Add.
|
|
356
306
|
self.api_auth_key = key
|
|
357
307
|
self.api_auth_sess_seconds = sess_seconds
|
|
358
|
-
self.app.include_router(
|
|
308
|
+
self.app.include_router(router_auth, tags=['auth'])
|
|
359
309
|
self.is_started_auth = True
|
|
360
310
|
|
|
361
|
-
## Admin.
|
|
362
|
-
self.add_admin_model(DatabaseORMTableUser, engine, category='auth', name='User', column_list=['user_id', 'name'])
|
|
363
|
-
self.add_admin_model(DatabaseORMTableRole, engine, category='auth', name='Role', column_list=['role_id', 'name'])
|
|
364
|
-
self.add_admin_model(DatabaseORMTablePerm, engine, category='auth', name='Perm', column_list=['perm_id', 'name'])
|
|
365
|
-
self.add_admin_model(DatabaseORMTableUserRole, engine, category='auth', name='User Role')
|
|
366
|
-
self.add_admin_model(DatabaseORMTableRolePerm, engine, category='auth', name='Role Perm')
|
|
367
|
-
|
|
368
311
|
|
|
369
312
|
def add_api_file(self, file_dir: str = 'file') -> None:
|
|
370
313
|
"""
|
|
@@ -374,26 +317,40 @@ class Server(ServerBase, Singleton):
|
|
|
374
317
|
Parameters
|
|
375
318
|
----------
|
|
376
319
|
file_dir : File API store directory path.
|
|
377
|
-
prefix : File API path prefix.
|
|
378
320
|
"""
|
|
379
321
|
|
|
380
|
-
from .rfile import
|
|
381
|
-
build_file_db,
|
|
382
|
-
file_router,
|
|
383
|
-
DatabaseORMTableInfo,
|
|
384
|
-
DatabaseORMTableData
|
|
385
|
-
)
|
|
322
|
+
from .rfile import build_db_file, router_file
|
|
386
323
|
|
|
387
324
|
# Database.
|
|
388
325
|
if 'file' not in self.db:
|
|
389
326
|
throw(AssertionError, self.db)
|
|
390
327
|
engine = self.db.file
|
|
391
|
-
|
|
328
|
+
build_db_file(engine)
|
|
392
329
|
|
|
393
330
|
# Add.
|
|
394
331
|
self.api_file_dir = file_dir
|
|
395
|
-
self.app.include_router(
|
|
332
|
+
self.app.include_router(router_file, tags=['file'], dependencies=(Bind.token,))
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
async def depend_server(request: Request) -> Server:
|
|
336
|
+
"""
|
|
337
|
+
Dependencie function of now Server instance.
|
|
338
|
+
|
|
339
|
+
Parameters
|
|
340
|
+
----------
|
|
341
|
+
request : Request.
|
|
342
|
+
|
|
343
|
+
Returns
|
|
344
|
+
-------
|
|
345
|
+
Server.
|
|
346
|
+
"""
|
|
347
|
+
|
|
348
|
+
# Get.
|
|
349
|
+
app: FastAPI = request.app
|
|
350
|
+
server = app.extra['server']
|
|
351
|
+
|
|
352
|
+
return server
|
|
353
|
+
|
|
396
354
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
self.add_admin_model(DatabaseORMTableData, engine, category='file', name='Data')
|
|
355
|
+
Bind.Server = Server
|
|
356
|
+
Bind.server = Bind.Depend(depend_server)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
reyserver/__init__.py,sha256=7GX64p7uI2eetJH9NJ-DTg-8iyQwOsGcviADFJCPxVA,373
|
|
2
|
+
reyserver/rall.py,sha256=riyDRTUsigco_Bee1H4aZFb8IgvjnxdX9qcnVb9i9mE,270
|
|
3
|
+
reyserver/rauth.py,sha256=JGo5lLTRGoP1SFPyQA-Q5EE-O3OU_86Mb3pvwhvFu-w,15125
|
|
4
|
+
reyserver/rbase.py,sha256=0ECJ1zuUpJhwaNc0u5GvR5VyGxIAnKQep-EdotwnLoE,7752
|
|
5
|
+
reyserver/rclient.py,sha256=og5YuWm-PODkFn9njBwYQpGlk0j1BfqFuEarlCFSQYI,6229
|
|
6
|
+
reyserver/rfile.py,sha256=6H5_7B9aiA3F56VToQDI9Trarkrl9gcIuFqYyCVCiCU,8877
|
|
7
|
+
reyserver/rserver.py,sha256=UQXzYKS-xdH1zRfLQIiZn253kqONWhBzZ4QGLu6GEoQ,9537
|
|
8
|
+
reyserver-1.1.62.dist-info/METADATA,sha256=RaK-segYQBiE3SqVbECLENKHd-1TOQFeYarUhtt6huI,1666
|
|
9
|
+
reyserver-1.1.62.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
10
|
+
reyserver-1.1.62.dist-info/licenses/LICENSE,sha256=UYLPqp7BvPiH8yEZduJqmmyEl6hlM3lKrFIefiD4rvk,1059
|
|
11
|
+
reyserver-1.1.62.dist-info/RECORD,,
|
reyserver/radmin.py
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
# !/usr/bin/env python
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
|
|
4
|
-
"""
|
|
5
|
-
@Time : 2025-10-14
|
|
6
|
-
@Author : Rey
|
|
7
|
-
@Contact : reyxbo@163.com
|
|
8
|
-
@Explain : Admin methods.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
from typing import Any, Type
|
|
13
|
-
from collections.abc import Sequence
|
|
14
|
-
from sqlalchemy.ext.asyncio import async_sessionmaker
|
|
15
|
-
from fastapi import Request
|
|
16
|
-
from sqladmin import Admin, ModelView
|
|
17
|
-
from sqladmin.authentication import AuthenticationBackend
|
|
18
|
-
from reydb import DatabaseEngineAsync, rorm
|
|
19
|
-
from reykit.rbase import Singleton
|
|
20
|
-
|
|
21
|
-
from .rbase import ServerBase
|
|
22
|
-
from . import rserver
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
__all__ = (
|
|
26
|
-
'ServerAdminModel',
|
|
27
|
-
'ServerAdminModelView',
|
|
28
|
-
'ServerAdminModelViewStats',
|
|
29
|
-
'ServerAdmin'
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class ServerAdminModel(ServerBase, ModelView):
|
|
34
|
-
"""
|
|
35
|
-
Server admin model type.
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
class ServerAdminModelView(ServerAdminModel):
|
|
40
|
-
"""
|
|
41
|
-
Server admin view model type.
|
|
42
|
-
"""
|
|
43
|
-
|
|
44
|
-
category = 'View'
|
|
45
|
-
can_view_details = can_edit = can_create = can_delete = False
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
class ServerAdminModelViewStats(ServerAdminModelView):
|
|
49
|
-
"""
|
|
50
|
-
Server admin stats view model type.
|
|
51
|
-
"""
|
|
52
|
-
|
|
53
|
-
column_list = ['item', 'value', 'comment']
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
# class ServerAdminAuthentication(ServerAdmin, AuthenticationBackend):
|
|
57
|
-
# """
|
|
58
|
-
# Server admin authentication type.
|
|
59
|
-
# """
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
# async def authenticate(self, request: Request) -> bool:
|
|
63
|
-
# """
|
|
64
|
-
# Authenticate request.
|
|
65
|
-
|
|
66
|
-
# Parameters
|
|
67
|
-
# ----------
|
|
68
|
-
# request : Request.
|
|
69
|
-
# """
|
|
70
|
-
|
|
71
|
-
# #
|
|
72
|
-
# ...
|
|
73
|
-
# request.session['token']
|
|
74
|
-
# ...
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
# async def login(self, request: Request) -> bool:
|
|
78
|
-
# form = await request.form()
|
|
79
|
-
# form['username']
|
|
80
|
-
# form['password']
|
|
81
|
-
# ...
|
|
82
|
-
# request.session['token'] = ...
|
|
83
|
-
# ...
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
# async def logout(self, request: Request) -> None:
|
|
87
|
-
# request.session.clear()
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
class ServerAdmin(ServerBase, Singleton):
|
|
91
|
-
"""
|
|
92
|
-
Server admin type, singleton mode.
|
|
93
|
-
"""
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def __init__(self, server: 'rserver.Server') -> None:
|
|
97
|
-
"""
|
|
98
|
-
Build instance attributes.
|
|
99
|
-
|
|
100
|
-
Parameters
|
|
101
|
-
----------
|
|
102
|
-
server : Server.
|
|
103
|
-
"""
|
|
104
|
-
|
|
105
|
-
# Build.
|
|
106
|
-
self.server = server
|
|
107
|
-
|
|
108
|
-
## Admin.
|
|
109
|
-
self.api_admin_binds: dict[ServerAdminModel, DatabaseEngineAsync] = {}
|
|
110
|
-
'Admin API model bind database engine dictionary.'
|
|
111
|
-
Session = async_sessionmaker()
|
|
112
|
-
Session.configure(binds=self.api_admin_binds)
|
|
113
|
-
auth = self.__create_auth()
|
|
114
|
-
self.admin = Admin(
|
|
115
|
-
self.server.app,
|
|
116
|
-
session_maker=Session,
|
|
117
|
-
authentication_backend=auth
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def add_model(
|
|
123
|
-
self,
|
|
124
|
-
model: Type[ServerAdminModel] | type[rorm.Model],
|
|
125
|
-
engine: DatabaseEngineAsync | str = None,
|
|
126
|
-
label: str | None = None,
|
|
127
|
-
name: str | None = None,
|
|
128
|
-
column: str | Sequence[str] | None = None,
|
|
129
|
-
**attrs: Any
|
|
130
|
-
) -> None:
|
|
131
|
-
"""
|
|
132
|
-
Add admin model type.
|
|
133
|
-
|
|
134
|
-
Parameters
|
|
135
|
-
----------
|
|
136
|
-
model : Model type.
|
|
137
|
-
- `type[rorm.Model]`: Define as `ServerAdminModel`.
|
|
138
|
-
engine : Database engine or name.
|
|
139
|
-
label : Admin model class label.
|
|
140
|
-
name : Admin model name.
|
|
141
|
-
column : Admin model display column names.
|
|
142
|
-
attrs : Other admin model attributes.
|
|
143
|
-
"""
|
|
144
|
-
|
|
145
|
-
# Parameter.
|
|
146
|
-
if issubclass(model, rorm.Model):
|
|
147
|
-
class ServerAdminModel_(ServerAdminModel, model=model): ...
|
|
148
|
-
model = ServerAdminModel_
|
|
149
|
-
if type(engine) == str:
|
|
150
|
-
engine = self.server.db[engine]
|
|
151
|
-
if label is not None:
|
|
152
|
-
model.category = label
|
|
153
|
-
if name is not None:
|
|
154
|
-
model.name = model.name_plural = name
|
|
155
|
-
if type(column) == str:
|
|
156
|
-
column = [column]
|
|
157
|
-
if column is not None:
|
|
158
|
-
model.column_list = list(column)
|
|
159
|
-
for key, value in attrs.items():
|
|
160
|
-
setattr(model, key, value)
|
|
161
|
-
|
|
162
|
-
# Add.
|
|
163
|
-
self.api_admin_binds[model.model] = engine.engine
|
|
164
|
-
self.admin.add_view(model)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
# Simple path.
|
|
168
|
-
|
|
169
|
-
## Server admin model type.
|
|
170
|
-
View = ServerAdminModelView
|
|
171
|
-
ViewStats = ServerAdminModelViewStats
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
reyserver/__init__.py,sha256=Oq-lOcQInzhgKt1_4OB2jNx0OO2d9qZg9iZqUx6YRr8,398
|
|
2
|
-
reyserver/radmin.py,sha256=0nOEBd1vFDn0annjiGXi3TN9C6Vofpb8W_UfXj5TuhM,4257
|
|
3
|
-
reyserver/rall.py,sha256=MI1NnqUpq22CK9w3XPPBS0K8lNuf0BnbI0pn4MGMx38,293
|
|
4
|
-
reyserver/rauth.py,sha256=KgHWuRmqmxMoeNFCvCpdxb0S5uv7VH75M0vuMrZTOt8,15111
|
|
5
|
-
reyserver/rbase.py,sha256=kzSbo6yoPlWquR6XiseTKi5hEbllaG3qVmmwnnKKac0,6898
|
|
6
|
-
reyserver/rclient.py,sha256=VDUqQMVUTGzmZ8kjiMet2_zEt0_Siw0gyQkqHRIZ594,6281
|
|
7
|
-
reyserver/rfile.py,sha256=0wS-ohsAOGcSCg07U66wpqJlxnCLWN6gkEQJK4qavWQ,8877
|
|
8
|
-
reyserver/rserver.py,sha256=mjc-SeE2RZZwB5Z1nUSMo_VMign3VcftIK1Kv7XIne8,11484
|
|
9
|
-
reyserver-1.1.60.dist-info/METADATA,sha256=JuZnt-TPamZk_dtV07w4aEm6cT5fZa3Js146QUa6wNI,1666
|
|
10
|
-
reyserver-1.1.60.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
-
reyserver-1.1.60.dist-info/licenses/LICENSE,sha256=UYLPqp7BvPiH8yEZduJqmmyEl6hlM3lKrFIefiD4rvk,1059
|
|
12
|
-
reyserver-1.1.60.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|