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 CHANGED
@@ -10,6 +10,7 @@
10
10
  Modules
11
11
  -------
12
12
  rall : All methods.
13
+ rauth : Authentication methods.
13
14
  rbase : Base methods.
14
15
  rclient : Client methods.
15
16
  rfile : File methods.
reyserver/rall.py CHANGED
@@ -9,6 +9,7 @@
9
9
  """
10
10
 
11
11
 
12
+ from .rauth import *
12
13
  from .rbase import *
13
14
  from .rclient import *
14
15
  from .rfile import *
reyserver/rauth.py ADDED
@@ -0,0 +1,11 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ @Time : 2025-10-10
6
+ @Author : Rey
7
+ @Contact : reyxbo@163.com
8
+ @Explain : Authentication methods.
9
+ """
10
+
11
+
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 reykit.rbase import CoroutineFunctionSimple, Base, ConfigMeta
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
- def create_lifespan(
45
- before: CoroutineFunctionSimple | Sequence[CoroutineFunctionSimple] | None = None,
46
- after: CoroutineFunctionSimple | Sequence[CoroutineFunctionSimple] | None = None,
47
- ):
60
+ class ServerExit(ServerBase, Exit):
61
+ """
62
+ Server exit type.
48
63
  """
49
- Create function of lifespan manager.
50
64
 
51
- Parameters
52
- ----------
53
- before : Execute before server start.
54
- after : Execute after server end.
65
+
66
+ class ServerExitHTTP(ServerExit, HTTPException):
67
+ """
68
+ Server HTTP exit type.
55
69
  """
56
70
 
57
- # Parameter.
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
- @asynccontextmanager
69
- async def lifespan(app: FastAPI):
74
+ def __init__(self, text: str | None = None):
70
75
  """
71
- Server lifespan manager.
76
+ Build instance attributes.
72
77
 
73
78
  Parameters
74
79
  ----------
75
- app : Server APP.
80
+ text : Explain text.
76
81
  """
77
82
 
78
- # Before.
79
- for task in before:
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
- return lifespan
92
+ status_code = status.HTTP_404_NOT_FOUND
89
93
 
90
94
 
91
- def create_depend_sess(database: str):
95
+ class ServerBind(ServerBase, metaclass=StaticMeta):
92
96
  """
93
- Create dependencie function of asynchronous database session.
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
- async def depend_sess():
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
- Dependencie function of asynchronous database session.
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
- engine = ServerConfig.server.db[database]
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
- # Context.
114
- async with engine.orm.session() as sess:
115
- yield sess
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
- return depend_sess
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 reykit.ros import File, get_md5
13
- from reykit.rnet import join_url, request
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, 'file', 'upload')
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
- # Upload.
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
- def download_file(self): ...
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
- def index_file(self): ...
185
+ return response_dict
reyserver/rfile.py CHANGED
@@ -9,18 +9,21 @@
9
9
  """
10
10
 
11
11
 
12
- from typing import Annotated
13
- from fastapi import APIRouter, Form, File, UploadFile, Depends
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, create_depend_sess
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 collections.abc import Sequence
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 fastapi import FastAPI, Depends
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, create_lifespan
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
- Depends(task)
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
- # debug=True
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
- ## API.
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
- ### File.
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 add_api_base(self):
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
- @self.app.get('/test')
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.45
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,,