reyserver 1.1.45__py3-none-any.whl → 1.1.47__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.
- reyserver/__init__.py +1 -0
- reyserver/rall.py +1 -0
- reyserver/rauth.py +11 -0
- reyserver/rbase.py +166 -51
- reyserver/rclient.py +89 -6
- reyserver/rfile.py +135 -59
- reyserver/rserver.py +99 -16
- {reyserver-1.1.45.dist-info → reyserver-1.1.47.dist-info}/METADATA +2 -1
- reyserver-1.1.47.dist-info/RECORD +11 -0
- reyserver-1.1.45.dist-info/RECORD +0 -10
- {reyserver-1.1.45.dist-info → reyserver-1.1.47.dist-info}/WHEEL +0 -0
- {reyserver-1.1.45.dist-info → reyserver-1.1.47.dist-info}/licenses/LICENSE +0 -0
reyserver/__init__.py
CHANGED
reyserver/rall.py
CHANGED
reyserver/rauth.py
ADDED
reyserver/rbase.py
CHANGED
@@ -9,11 +9,24 @@
|
|
9
9
|
"""
|
10
10
|
|
11
11
|
|
12
|
-
from typing import Sequence
|
12
|
+
from typing import Sequence, Literal
|
13
13
|
from inspect import iscoroutinefunction
|
14
|
-
from contextlib import asynccontextmanager
|
15
|
-
from fastapi import FastAPI
|
16
|
-
from
|
14
|
+
from contextlib import asynccontextmanager, _AsyncGeneratorContextManager
|
15
|
+
from fastapi import FastAPI, HTTPException, status, UploadFile as File
|
16
|
+
from fastapi.params import (
|
17
|
+
Depends,
|
18
|
+
Path,
|
19
|
+
Query,
|
20
|
+
Header,
|
21
|
+
Cookie,
|
22
|
+
Body,
|
23
|
+
Form,
|
24
|
+
File as Forms
|
25
|
+
)
|
26
|
+
from reydb.rconn import DatabaseConnectionAsync
|
27
|
+
from reydb.rorm import DatabaseORMModel, DatabaseORMSessionAsync
|
28
|
+
from reykit.rwrap import wrap_cache
|
29
|
+
from reykit.rbase import CoroutineFunctionSimple, Base, Exit, StaticMeta, ConfigMeta
|
17
30
|
|
18
31
|
from . import rserver
|
19
32
|
|
@@ -21,6 +34,9 @@ from . import rserver
|
|
21
34
|
__all__ = (
|
22
35
|
'ServerBase',
|
23
36
|
'ServerConfig',
|
37
|
+
'ServerExit',
|
38
|
+
'ServerExitHTTP',
|
39
|
+
'ServerExitHTTP404',
|
24
40
|
'create_lifespan',
|
25
41
|
'create_depend_sess'
|
26
42
|
)
|
@@ -41,78 +57,177 @@ class ServerConfig(ServerBase, metaclass=ConfigMeta):
|
|
41
57
|
'Server instance.'
|
42
58
|
|
43
59
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
):
|
60
|
+
class ServerExit(ServerBase, Exit):
|
61
|
+
"""
|
62
|
+
Server exit type.
|
48
63
|
"""
|
49
|
-
Create function of lifespan manager.
|
50
64
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
65
|
+
|
66
|
+
class ServerExitHTTP(ServerExit, HTTPException):
|
67
|
+
"""
|
68
|
+
Server HTTP exit type.
|
55
69
|
"""
|
56
70
|
|
57
|
-
|
58
|
-
if before is None:
|
59
|
-
before = ()
|
60
|
-
elif iscoroutinefunction(before):
|
61
|
-
before = (before,)
|
62
|
-
if after is None:
|
63
|
-
after = ()
|
64
|
-
elif iscoroutinefunction(after):
|
65
|
-
after = (after,)
|
71
|
+
status_code: int
|
66
72
|
|
67
73
|
|
68
|
-
|
69
|
-
async def lifespan(app: FastAPI):
|
74
|
+
def __init__(self, text: str | None = None):
|
70
75
|
"""
|
71
|
-
|
76
|
+
Build instance attributes.
|
72
77
|
|
73
78
|
Parameters
|
74
79
|
----------
|
75
|
-
|
80
|
+
text : Explain text.
|
76
81
|
"""
|
77
82
|
|
78
|
-
#
|
79
|
-
|
80
|
-
await task()
|
81
|
-
yield
|
83
|
+
# Super.
|
84
|
+
super().__init__(self.status_code, text)
|
82
85
|
|
83
|
-
# After.
|
84
|
-
for task in after:
|
85
|
-
await after()
|
86
86
|
|
87
|
+
class ServerExitHTTP404(ServerExitHTTP):
|
88
|
+
"""
|
89
|
+
Server HTTP 404 exit type.
|
90
|
+
"""
|
87
91
|
|
88
|
-
|
92
|
+
status_code = status.HTTP_404_NOT_FOUND
|
89
93
|
|
90
94
|
|
91
|
-
|
95
|
+
class ServerBind(ServerBase, metaclass=StaticMeta):
|
92
96
|
"""
|
93
|
-
|
94
|
-
|
95
|
-
Parameters
|
96
|
-
----------
|
97
|
-
database : Database name.
|
98
|
-
|
99
|
-
Returns
|
100
|
-
-------
|
101
|
-
Dependencie function.
|
97
|
+
Server API bind parameter type.
|
102
98
|
"""
|
103
99
|
|
104
100
|
|
105
|
-
|
101
|
+
def create_lifespan(
|
102
|
+
before: CoroutineFunctionSimple | Sequence[CoroutineFunctionSimple] | None = None,
|
103
|
+
after: CoroutineFunctionSimple | Sequence[CoroutineFunctionSimple] | None = None,
|
104
|
+
) -> _AsyncGeneratorContextManager[None, None]:
|
106
105
|
"""
|
107
|
-
|
106
|
+
Create asynchronous function of lifespan manager.
|
107
|
+
|
108
|
+
Parameters
|
109
|
+
----------
|
110
|
+
before : Execute before server start.
|
111
|
+
after : Execute after server end.
|
112
|
+
|
113
|
+
Returns
|
114
|
+
-------
|
115
|
+
Asynchronous function.
|
108
116
|
"""
|
109
117
|
|
110
118
|
# Parameter.
|
111
|
-
|
119
|
+
if before is None:
|
120
|
+
before = ()
|
121
|
+
elif iscoroutinefunction(before):
|
122
|
+
before = (before,)
|
123
|
+
if after is None:
|
124
|
+
after = ()
|
125
|
+
elif iscoroutinefunction(after):
|
126
|
+
after = (after,)
|
127
|
+
|
112
128
|
|
113
|
-
|
114
|
-
async
|
115
|
-
|
129
|
+
@asynccontextmanager
|
130
|
+
async def lifespan(app: FastAPI):
|
131
|
+
"""
|
132
|
+
Server lifespan manager.
|
133
|
+
|
134
|
+
Parameters
|
135
|
+
----------
|
136
|
+
app : Server APP.
|
137
|
+
"""
|
138
|
+
|
139
|
+
# Before.
|
140
|
+
for task in before:
|
141
|
+
await task()
|
142
|
+
yield
|
143
|
+
|
144
|
+
# After.
|
145
|
+
for task in after:
|
146
|
+
await after()
|
147
|
+
|
148
|
+
|
149
|
+
return lifespan
|
150
|
+
|
151
|
+
|
152
|
+
@wrap_cache
|
153
|
+
def create_depend_db(database: str, mode: Literal['sess', 'conn']) -> Depends:
|
154
|
+
"""
|
155
|
+
Create dependencie type of asynchronous database.
|
156
|
+
|
157
|
+
Parameters
|
158
|
+
----------
|
159
|
+
database : Database name.
|
160
|
+
mode : Mode.
|
161
|
+
- `Literl['sess']`: Create ORM session instance.
|
162
|
+
- `Literl['conn']`: Create connection instance.
|
163
|
+
|
164
|
+
Returns
|
165
|
+
-------
|
166
|
+
Dependencie type.
|
167
|
+
"""
|
116
168
|
|
117
169
|
|
118
|
-
|
170
|
+
async def depend_db():
|
171
|
+
"""
|
172
|
+
Dependencie function of asynchronous database.
|
173
|
+
"""
|
174
|
+
|
175
|
+
# Parameter.
|
176
|
+
engine = ServerConfig.server.db[database]
|
177
|
+
|
178
|
+
# Context.
|
179
|
+
if mode == 'sess':
|
180
|
+
async with engine.orm.session() as sess:
|
181
|
+
yield sess
|
182
|
+
elif mode == 'conn':
|
183
|
+
async with engine.connect() as conn:
|
184
|
+
yield conn
|
185
|
+
|
186
|
+
|
187
|
+
# Create.
|
188
|
+
depend = Depends(depend_db)
|
189
|
+
|
190
|
+
return depend
|
191
|
+
|
192
|
+
|
193
|
+
Depend = Depends
|
194
|
+
Path = Path
|
195
|
+
Query = Query
|
196
|
+
Header = Header
|
197
|
+
Cookie = Cookie
|
198
|
+
Body = Body
|
199
|
+
Form = Form
|
200
|
+
Forms = Forms
|
201
|
+
File = File
|
202
|
+
JSON = DatabaseORMModel
|
203
|
+
Conn = DatabaseConnectionAsync
|
204
|
+
Sess = DatabaseORMSessionAsync
|
205
|
+
path = Path()
|
206
|
+
'Path instance.'
|
207
|
+
query = Query()
|
208
|
+
'Query instance.'
|
209
|
+
header = Header()
|
210
|
+
'Header instance.'
|
211
|
+
cookie = Cookie()
|
212
|
+
'Cookie instance.'
|
213
|
+
body = Body()
|
214
|
+
'Body instance.'
|
215
|
+
form = Form()
|
216
|
+
'Form instance.'
|
217
|
+
forms = Forms()
|
218
|
+
'Forms instance.'
|
219
|
+
query_n = Query(None)
|
220
|
+
'Query instance, default `None`.'
|
221
|
+
header_n = Header(None)
|
222
|
+
'Header instance, default `None`.'
|
223
|
+
cookie_n = Cookie(None)
|
224
|
+
'Cookie instance, default `None`.'
|
225
|
+
body_n = Body(None)
|
226
|
+
'Body instance, default `None`.'
|
227
|
+
form_n = Form(None)
|
228
|
+
'Form instance, default `None`.'
|
229
|
+
forms_n = Forms(None)
|
230
|
+
'Forms instance, default `None`.'
|
231
|
+
|
232
|
+
|
233
|
+
Bind = ServerBind
|
reyserver/rclient.py
CHANGED
@@ -9,8 +9,10 @@
|
|
9
9
|
"""
|
10
10
|
|
11
11
|
|
12
|
-
from
|
13
|
-
from
|
12
|
+
from typing import TypedDict
|
13
|
+
from datetime import datetime as Datetime
|
14
|
+
from reykit.ros import File, Folder, overload
|
15
|
+
from reykit.rnet import join_url, request, get_response_file_name
|
14
16
|
|
15
17
|
from .rbase import ServerBase
|
16
18
|
|
@@ -20,6 +22,9 @@ __all__ = (
|
|
20
22
|
)
|
21
23
|
|
22
24
|
|
25
|
+
FileInfo = TypedDict('FileInfo', {'create_time': Datetime, 'md5': str, 'name': str | None, 'size': int, 'note': str | None})
|
26
|
+
|
27
|
+
|
23
28
|
class ServerClient(ServerBase):
|
24
29
|
"""
|
25
30
|
Server client type.
|
@@ -64,7 +69,7 @@ class ServerClient(ServerBase):
|
|
64
69
|
"""
|
65
70
|
|
66
71
|
# Handle parameter.
|
67
|
-
url = join_url(self.url, '
|
72
|
+
url = join_url(self.url, 'files')
|
68
73
|
match source:
|
69
74
|
|
70
75
|
## File path.
|
@@ -84,7 +89,7 @@ class ServerClient(ServerBase):
|
|
84
89
|
if name is not None:
|
85
90
|
file_name = name
|
86
91
|
|
87
|
-
#
|
92
|
+
# Request.
|
88
93
|
data = {'name': file_name, 'note': note}
|
89
94
|
files = {'file': file_bytes}
|
90
95
|
response = request(url, data=data, files=files, check=True)
|
@@ -96,7 +101,85 @@ class ServerClient(ServerBase):
|
|
96
101
|
return file_id
|
97
102
|
|
98
103
|
|
99
|
-
|
104
|
+
@overload
|
105
|
+
def download_file(
|
106
|
+
self,
|
107
|
+
file_id: int,
|
108
|
+
path: None = None
|
109
|
+
) -> bytes: ...
|
110
|
+
|
111
|
+
@overload
|
112
|
+
def download_file(
|
113
|
+
self,
|
114
|
+
file_id: int,
|
115
|
+
path: str
|
116
|
+
) -> str: ...
|
117
|
+
|
118
|
+
def download_file(
|
119
|
+
self,
|
120
|
+
file_id: int,
|
121
|
+
path: str | None = None
|
122
|
+
) -> bytes | str:
|
123
|
+
"""
|
124
|
+
Download file.
|
125
|
+
|
126
|
+
Parameters
|
127
|
+
----------
|
128
|
+
file_id : File ID.
|
129
|
+
path : File save path.
|
130
|
+
- `None`: Not save and return file bytes.
|
131
|
+
- `str`: Save and return file path.
|
132
|
+
`File path`: Use this file path.
|
133
|
+
`Folder path`: Use this folder path and original name.
|
134
|
+
|
135
|
+
Returns
|
136
|
+
-------
|
137
|
+
File bytes or file path.
|
138
|
+
"""
|
139
|
+
|
140
|
+
# Parameter.
|
141
|
+
url = join_url(self.url, 'files', file_id, 'download')
|
142
|
+
|
143
|
+
# Request.
|
144
|
+
response = request(url, check=True)
|
145
|
+
file_bytes = response.content
|
146
|
+
|
147
|
+
# Not save.
|
148
|
+
if path is None:
|
149
|
+
return file_bytes
|
150
|
+
|
151
|
+
# Save.
|
152
|
+
else:
|
153
|
+
folder = Folder(path)
|
154
|
+
if folder:
|
155
|
+
file_name = get_response_file_name(response)
|
156
|
+
path = folder + file_name
|
157
|
+
file = File(path)
|
158
|
+
file(file_bytes)
|
159
|
+
return file.path
|
160
|
+
|
161
|
+
|
162
|
+
def get_file_info(
|
163
|
+
self,
|
164
|
+
file_id: int
|
165
|
+
) -> FileInfo:
|
166
|
+
"""
|
167
|
+
Query file information.
|
168
|
+
|
169
|
+
Parameters
|
170
|
+
----------
|
171
|
+
file_id : File ID.
|
172
|
+
|
173
|
+
Returns
|
174
|
+
-------
|
175
|
+
File information.
|
176
|
+
"""
|
177
|
+
|
178
|
+
# Parameter.
|
179
|
+
url = join_url(self.url, 'files', file_id)
|
100
180
|
|
181
|
+
# Request.
|
182
|
+
response = request(url, check=True)
|
183
|
+
response_dict = response.json()
|
101
184
|
|
102
|
-
|
185
|
+
return response_dict
|
reyserver/rfile.py
CHANGED
@@ -9,18 +9,21 @@
|
|
9
9
|
"""
|
10
10
|
|
11
11
|
|
12
|
-
from
|
13
|
-
from fastapi import
|
12
|
+
from fastapi import APIRouter
|
13
|
+
from fastapi.responses import FileResponse
|
14
14
|
from reydb import rorm
|
15
|
+
from reydb.rorm import DatabaseORMSessionAsync
|
16
|
+
from reydb.rconn import DatabaseConnectionAsync
|
15
17
|
from reykit.ros import FileStore, get_md5
|
16
18
|
|
17
|
-
from .rbase import ServerConfig,
|
19
|
+
from .rbase import ServerConfig, ServerExitHTTP404, Bind
|
18
20
|
|
19
21
|
|
20
22
|
__all__ = (
|
21
23
|
'DatabaseORMTableInfo',
|
22
24
|
'DatabaseORMTableData',
|
23
|
-
'build_file_db'
|
25
|
+
'build_file_db',
|
26
|
+
'file_router'
|
24
27
|
)
|
25
28
|
|
26
29
|
|
@@ -50,61 +53,6 @@ class DatabaseORMTableData(rorm.Model, table=True):
|
|
50
53
|
path: str = rorm.Field(rorm.types.VARCHAR(4095), not_null=True, comment='File disk storage path.')
|
51
54
|
|
52
55
|
|
53
|
-
file_router = APIRouter()
|
54
|
-
depend_sess_file = create_depend_sess('file')
|
55
|
-
|
56
|
-
|
57
|
-
@file_router.post('/upload')
|
58
|
-
async def upload(
|
59
|
-
file: Annotated[UploadFile, File()],
|
60
|
-
name: Annotated[str, Form()],
|
61
|
-
note: Annotated[str, Form()],
|
62
|
-
sess: Annotated[rorm.DatabaseORMSessionAsync, Depends(depend_sess_file)]
|
63
|
-
):
|
64
|
-
"""
|
65
|
-
Upload file.
|
66
|
-
|
67
|
-
Parameters
|
68
|
-
----------
|
69
|
-
file : File instance.
|
70
|
-
note : File note.
|
71
|
-
sess : Asynchronous database session.
|
72
|
-
"""
|
73
|
-
|
74
|
-
# Handle parameter.
|
75
|
-
file_store = FileStore(ServerConfig.server.api_file_dir)
|
76
|
-
file_bytes = await file.read()
|
77
|
-
file_md5 = get_md5(file_bytes)
|
78
|
-
file_size = len(file_bytes)
|
79
|
-
|
80
|
-
# Upload.
|
81
|
-
file_path = file_store.index(file_md5)
|
82
|
-
|
83
|
-
## Data.
|
84
|
-
if file_path is None:
|
85
|
-
file_path = file_store.store(file_bytes)
|
86
|
-
table_data = DatabaseORMTableData(
|
87
|
-
md5=file_md5,
|
88
|
-
size=file_size,
|
89
|
-
path=file_path
|
90
|
-
)
|
91
|
-
await sess.add(table_data)
|
92
|
-
|
93
|
-
## Information.
|
94
|
-
table_info = DatabaseORMTableInfo(
|
95
|
-
md5=file_md5,
|
96
|
-
name=name,
|
97
|
-
note=note
|
98
|
-
)
|
99
|
-
await sess.add(table_info)
|
100
|
-
|
101
|
-
# Get ID.
|
102
|
-
await sess.flush()
|
103
|
-
file_id = table_info.file_id
|
104
|
-
|
105
|
-
return {'file_id': file_id}
|
106
|
-
|
107
|
-
|
108
56
|
def build_file_db() -> None:
|
109
57
|
"""
|
110
58
|
Check and build `file` database tables.
|
@@ -226,3 +174,131 @@ def build_file_db() -> None:
|
|
226
174
|
|
227
175
|
# Build.
|
228
176
|
engine.sync_engine.build.build(tables=tables, views=views, views_stats=views_stats, skip=True)
|
177
|
+
|
178
|
+
|
179
|
+
file_router = APIRouter()
|
180
|
+
depend_file_sess = Bind.create_depend_db('file', 'sess')
|
181
|
+
depend_file_conn = Bind.create_depend_db('file', 'conn')
|
182
|
+
|
183
|
+
|
184
|
+
@file_router.post('/')
|
185
|
+
async def upload_file(
|
186
|
+
file: Bind.File = Bind.forms,
|
187
|
+
name: str = Bind.forms_n,
|
188
|
+
note: str = Bind.forms_n,
|
189
|
+
sess: Bind.Sess = depend_file_sess
|
190
|
+
) -> DatabaseORMTableInfo:
|
191
|
+
"""
|
192
|
+
Upload file.
|
193
|
+
|
194
|
+
Parameters
|
195
|
+
----------
|
196
|
+
file : File instance.
|
197
|
+
name : File name.
|
198
|
+
note : File note.
|
199
|
+
|
200
|
+
Returns
|
201
|
+
-------
|
202
|
+
File information.
|
203
|
+
"""
|
204
|
+
|
205
|
+
# Handle parameter.
|
206
|
+
file_store = FileStore(ServerConfig.server.api_file_dir)
|
207
|
+
file_bytes = await file.read()
|
208
|
+
file_md5 = get_md5(file_bytes)
|
209
|
+
file_size = len(file_bytes)
|
210
|
+
|
211
|
+
# Upload.
|
212
|
+
file_path = file_store.index(file_md5)
|
213
|
+
|
214
|
+
## Data.
|
215
|
+
if file_path is None:
|
216
|
+
file_path = file_store.store(file_bytes)
|
217
|
+
table_data = DatabaseORMTableData(
|
218
|
+
md5=file_md5,
|
219
|
+
size=file_size,
|
220
|
+
path=file_path
|
221
|
+
)
|
222
|
+
await sess.add(table_data)
|
223
|
+
|
224
|
+
## Information.
|
225
|
+
table_info = DatabaseORMTableInfo(
|
226
|
+
md5=file_md5,
|
227
|
+
name=name,
|
228
|
+
note=note
|
229
|
+
)
|
230
|
+
await sess.add(table_info)
|
231
|
+
|
232
|
+
# Get ID.
|
233
|
+
await sess.flush()
|
234
|
+
|
235
|
+
return table_info
|
236
|
+
|
237
|
+
|
238
|
+
@file_router.get('/{file_id}/download')
|
239
|
+
async def download_file(
|
240
|
+
file_id: int = Bind.path,
|
241
|
+
conn: Bind.Conn = depend_file_conn
|
242
|
+
) -> FileResponse:
|
243
|
+
"""
|
244
|
+
Download file.
|
245
|
+
|
246
|
+
Parameters
|
247
|
+
----------
|
248
|
+
file_id : File ID.
|
249
|
+
|
250
|
+
Returns
|
251
|
+
-------
|
252
|
+
File data.
|
253
|
+
"""
|
254
|
+
|
255
|
+
# Search.
|
256
|
+
sql = (
|
257
|
+
'SELECT `name`, (\n'
|
258
|
+
' SELECT `path`\n'
|
259
|
+
f' FROM `{conn.engine.database}`.`data`\n'
|
260
|
+
f' WHERE `md5` = `info`.`md5`\n'
|
261
|
+
' LIMIT 1\n'
|
262
|
+
') AS `path`\n'
|
263
|
+
f'FROM `{conn.engine.database}`.`info`\n'
|
264
|
+
'WHERE `file_id` = :file_id\n'
|
265
|
+
'LIMIT 1'
|
266
|
+
)
|
267
|
+
result = await conn.execute(sql, file_id=file_id)
|
268
|
+
|
269
|
+
# Check.
|
270
|
+
if result.empty:
|
271
|
+
raise ServerExitHTTP404("file ID '%s' not exist" % file_id)
|
272
|
+
file_name, file_path = result.first()
|
273
|
+
|
274
|
+
# Response.
|
275
|
+
response = FileResponse(file_path, filename=file_name)
|
276
|
+
|
277
|
+
return response
|
278
|
+
|
279
|
+
|
280
|
+
@file_router.get('/{file_id}')
|
281
|
+
async def get_file_info(
|
282
|
+
file_id: int = Bind.path,
|
283
|
+
sess: Bind.Sess = depend_file_sess
|
284
|
+
) -> DatabaseORMTableInfo:
|
285
|
+
"""
|
286
|
+
Get file information.
|
287
|
+
|
288
|
+
Parameters
|
289
|
+
----------
|
290
|
+
file_id : File ID.
|
291
|
+
|
292
|
+
Returns
|
293
|
+
-------
|
294
|
+
File information.
|
295
|
+
"""
|
296
|
+
|
297
|
+
# Get.
|
298
|
+
table_info = await sess.get(DatabaseORMTableInfo, file_id)
|
299
|
+
|
300
|
+
# Check.
|
301
|
+
if table_info is None:
|
302
|
+
raise ServerExitHTTP404("file ID '%s' not exist" % file_id)
|
303
|
+
|
304
|
+
return table_info
|
reyserver/rserver.py
CHANGED
@@ -9,15 +9,19 @@
|
|
9
9
|
"""
|
10
10
|
|
11
11
|
|
12
|
-
from
|
12
|
+
from typing import Literal
|
13
|
+
from collections.abc import Sequence, Callable, Coroutine
|
13
14
|
from inspect import iscoroutinefunction
|
14
15
|
from uvicorn import run as uvicorn_run
|
15
|
-
from
|
16
|
+
from starlette.middleware.base import _StreamingResponse
|
17
|
+
from fastapi import FastAPI, Request
|
16
18
|
from fastapi.staticfiles import StaticFiles
|
19
|
+
from fastapi.middleware.gzip import GZipMiddleware
|
20
|
+
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
|
17
21
|
from reydb import DatabaseAsync
|
18
|
-
from reykit.rbase import CoroutineFunctionSimple, Singleton, throw
|
22
|
+
from reykit.rbase import CallableT, CoroutineFunctionSimple, Singleton, throw
|
19
23
|
|
20
|
-
from .rbase import ServerBase, ServerConfig,
|
24
|
+
from .rbase import ServerBase, ServerConfig, Bind
|
21
25
|
|
22
26
|
|
23
27
|
__all__ = (
|
@@ -42,31 +46,33 @@ class Server(ServerBase, Singleton):
|
|
42
46
|
after: CoroutineFunctionSimple | Sequence[CoroutineFunctionSimple] | None = None,
|
43
47
|
ssl_cert: str | None = None,
|
44
48
|
ssl_key: str | None = None,
|
49
|
+
debug: bool = False
|
45
50
|
) -> None:
|
46
51
|
"""
|
47
52
|
Build instance attributes.
|
48
53
|
|
49
54
|
Parameters
|
50
55
|
----------
|
51
|
-
db : Asynchronous database.
|
56
|
+
db : Asynchronous database, must include database engines with APIs.
|
52
57
|
public : Public directory.
|
53
58
|
depend : Global api dependencies.
|
54
59
|
before : Execute before server start.
|
55
60
|
after : Execute after server end.
|
56
61
|
ssl_cert : SSL certificate file path.
|
57
62
|
ssl_key : SSL key file path.
|
63
|
+
debug : Whether use development mode debug server.
|
58
64
|
"""
|
59
65
|
|
60
66
|
# Parameter.
|
61
67
|
if type(ssl_cert) != type(ssl_key):
|
62
68
|
throw(AssertionError, ssl_cert, ssl_key)
|
63
|
-
lifespan = create_lifespan(before, after)
|
69
|
+
lifespan = Bind.create_lifespan(before, after)
|
64
70
|
if depend is None:
|
65
71
|
depend = ()
|
66
72
|
elif iscoroutinefunction(depend):
|
67
73
|
depend = (depend,)
|
68
74
|
depend = [
|
69
|
-
|
75
|
+
Bind.Depend(task)
|
70
76
|
for task in depend
|
71
77
|
]
|
72
78
|
|
@@ -80,18 +86,57 @@ class Server(ServerBase, Singleton):
|
|
80
86
|
self.app = FastAPI(
|
81
87
|
dependencies=depend,
|
82
88
|
lifespan=lifespan,
|
83
|
-
|
89
|
+
debug=debug
|
84
90
|
)
|
85
91
|
|
86
|
-
## Static.
|
87
92
|
if public is not None:
|
88
93
|
subapp = StaticFiles(directory=public, html=True)
|
89
94
|
self.app.mount('/', subapp)
|
95
|
+
self.wrap_middleware = self.app.middleware('http')
|
96
|
+
'Decorator, add middleware to APP.'
|
90
97
|
|
91
|
-
|
98
|
+
# Middleware
|
99
|
+
self.app.add_middleware(GZipMiddleware)
|
100
|
+
if not debug:
|
101
|
+
self.app.add_middleware(HTTPSRedirectMiddleware)
|
102
|
+
self.__add_default_middleware()
|
92
103
|
|
93
|
-
|
104
|
+
# API.
|
94
105
|
self.api_file_dir: str
|
106
|
+
'File API store directory path.'
|
107
|
+
|
108
|
+
|
109
|
+
def __add_default_middleware(self) -> None:
|
110
|
+
"""
|
111
|
+
Add default handle middleware.
|
112
|
+
"""
|
113
|
+
|
114
|
+
# Add.
|
115
|
+
@self.wrap_middleware
|
116
|
+
async def foo(
|
117
|
+
request: Request,
|
118
|
+
call_next: Callable[[Request], Coroutine[None, None, _StreamingResponse]]
|
119
|
+
) -> _StreamingResponse:
|
120
|
+
"""
|
121
|
+
Default handle middleware.
|
122
|
+
|
123
|
+
Parameters
|
124
|
+
----------
|
125
|
+
Reqeust : Request instance.
|
126
|
+
call_next : Next middleware.
|
127
|
+
"""
|
128
|
+
|
129
|
+
# Before.
|
130
|
+
...
|
131
|
+
|
132
|
+
# Next.
|
133
|
+
response = await call_next(request)
|
134
|
+
|
135
|
+
# After.
|
136
|
+
if request.method == 'POST':
|
137
|
+
response.status_code = 201
|
138
|
+
|
139
|
+
return response
|
95
140
|
|
96
141
|
|
97
142
|
def run(self) -> None:
|
@@ -107,16 +152,54 @@ class Server(ServerBase, Singleton):
|
|
107
152
|
)
|
108
153
|
|
109
154
|
|
110
|
-
def
|
155
|
+
def set_doc(
|
156
|
+
self,
|
157
|
+
version: str | None = None,
|
158
|
+
title: str | None = None,
|
159
|
+
summary: str | None = None,
|
160
|
+
desc: str | None = None,
|
161
|
+
contact: dict[Literal['name', 'email', 'url'], str] | None = None
|
162
|
+
) -> None:
|
163
|
+
"""
|
164
|
+
Set server document.
|
165
|
+
|
166
|
+
Parameters
|
167
|
+
----------
|
168
|
+
version : Server version.
|
169
|
+
title : Server title.
|
170
|
+
summary : Server summary.
|
171
|
+
desc : Server description.
|
172
|
+
contact : Server contact information.
|
173
|
+
"""
|
174
|
+
|
175
|
+
# Parameter.
|
176
|
+
set_dict = {
|
177
|
+
'version': version,
|
178
|
+
'title': title,
|
179
|
+
'summary': summary,
|
180
|
+
'description': desc,
|
181
|
+
'contact': contact
|
182
|
+
}
|
183
|
+
|
184
|
+
# Set.
|
185
|
+
for key, value in set_dict.items():
|
186
|
+
if value is not None:
|
187
|
+
setattr(self.app, key, value)
|
188
|
+
|
189
|
+
|
190
|
+
def add_api_auth(self):
|
191
|
+
"""
|
192
|
+
Add Authentication API.
|
193
|
+
Note: must include database engine of `auth` name.
|
194
|
+
"""
|
111
195
|
|
112
|
-
|
113
|
-
async def test():
|
114
|
-
return {'message': 'test'}
|
196
|
+
...
|
115
197
|
|
116
198
|
|
117
199
|
def add_api_file(self, file_dir: str = 'file') -> None:
|
118
200
|
"""
|
119
201
|
Add file API.
|
202
|
+
Note: must include database engine of `file` name.
|
120
203
|
|
121
204
|
Parameters
|
122
205
|
----------
|
@@ -131,4 +214,4 @@ class Server(ServerBase, Singleton):
|
|
131
214
|
|
132
215
|
# Add.
|
133
216
|
self.api_file_dir = file_dir
|
134
|
-
self.app.include_router(file_router, prefix='/file')
|
217
|
+
self.app.include_router(file_router, prefix='/files', tags=['file'])
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: reyserver
|
3
|
-
Version: 1.1.
|
3
|
+
Version: 1.1.47
|
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>
|
@@ -14,6 +14,7 @@ License: Copyright 2025 ReyXBo
|
|
14
14
|
License-File: LICENSE
|
15
15
|
Keywords: API,async,asynchronous,backend,rey,reyxbo,server
|
16
16
|
Requires-Python: >=3.12
|
17
|
+
Requires-Dist: email-validator
|
17
18
|
Requires-Dist: fastapi
|
18
19
|
Requires-Dist: python-multipart
|
19
20
|
Requires-Dist: reydb
|
@@ -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=QyY4gZ0ulpH9Kxvux_jnZBhzfWZZEuOWB1oYU9uzCnY,167
|
4
|
+
reyserver/rbase.py,sha256=nJ0x4hm_YZN210quppSFu_6mRNKDLLNyS27jzSc0Yg8,5220
|
5
|
+
reyserver/rclient.py,sha256=pTJtn78jPKgFo5EoQwZRdM0cYHdCs7QUKqfl-jUBRgk,4220
|
6
|
+
reyserver/rfile.py,sha256=5ynG0LlJLdvNiuFBBBuZhT-0jf_1LxEzac55Q40bsk8,9086
|
7
|
+
reyserver/rserver.py,sha256=hqpemzJHO6xHy_7pO3cvvjnfy8Yfqy8HfyIq4sjk4Dc,5889
|
8
|
+
reyserver-1.1.47.dist-info/METADATA,sha256=gav3pRrTdUZXsbSxo-3JkNjlzhzhasdolU8iywyKQmc,1689
|
9
|
+
reyserver-1.1.47.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
10
|
+
reyserver-1.1.47.dist-info/licenses/LICENSE,sha256=UYLPqp7BvPiH8yEZduJqmmyEl6hlM3lKrFIefiD4rvk,1059
|
11
|
+
reyserver-1.1.47.dist-info/RECORD,,
|
@@ -1,10 +0,0 @@
|
|
1
|
-
reyserver/__init__.py,sha256=JmDkcdP7oxmehE53rRRL_zhxmPinAbJRONpQp5qhsGI,340
|
2
|
-
reyserver/rall.py,sha256=6vqacf9erEfVfK5sgGJMDXD1h4ebt5gDN7NrPdtELms,248
|
3
|
-
reyserver/rbase.py,sha256=khBk6N0G33hstiisDWtz4Tdg1tvnFrGszOynBtnKxsI,2305
|
4
|
-
reyserver/rclient.py,sha256=OjY_OEH_USrJK8TuCevXG6jNPVbM0lN-xMBozB7kSM8,2195
|
5
|
-
reyserver/rfile.py,sha256=wf56tItty7Jbjcwvh1yMgcX2jlSSXAYbTDKnA5dI8Yw,7459
|
6
|
-
reyserver/rserver.py,sha256=SfGs0W6StFtRtJ_UYI_avlL99euw7VvP55248mSOjLw,3387
|
7
|
-
reyserver-1.1.45.dist-info/METADATA,sha256=N4nGN_EC1Czdd7Q5DIHr4mxM3NNiPQCgjDenIZJ25yg,1658
|
8
|
-
reyserver-1.1.45.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
9
|
-
reyserver-1.1.45.dist-info/licenses/LICENSE,sha256=UYLPqp7BvPiH8yEZduJqmmyEl6hlM3lKrFIefiD4rvk,1059
|
10
|
-
reyserver-1.1.45.dist-info/RECORD,,
|
File without changes
|
File without changes
|