reyserver 1.1.55__py3-none-any.whl → 1.1.57__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 CHANGED
@@ -9,6 +9,7 @@
9
9
 
10
10
  Modules
11
11
  -------
12
+ radmin : Admin methods.
12
13
  rall : All methods.
13
14
  rauth : Authentication methods.
14
15
  rbase : Base methods.
reyserver/radmin.py ADDED
@@ -0,0 +1,128 @@
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 sqladmin import Admin, ModelView
16
+ from reydb import DatabaseEngineAsync, rorm
17
+ from reykit.rbase import Singleton
18
+
19
+ from .rbase import ServerBase
20
+ from . import rserver
21
+
22
+
23
+ __all__ = (
24
+ 'ServerAdminModel',
25
+ 'ServerAdminModelView',
26
+ 'ServerAdminModelViewStats'
27
+ )
28
+
29
+
30
+ class ServerAdminModel(ModelView):
31
+ """
32
+ Server admin model type.
33
+ """
34
+
35
+
36
+ class ServerAdminModelView(ServerAdminModel):
37
+ """
38
+ Server admin view model type.
39
+ """
40
+
41
+ category = 'View'
42
+ can_view_details = can_edit = can_create = can_delete = False
43
+
44
+
45
+ class ServerAdminModelViewStats(ServerAdminModelView):
46
+ """
47
+ Server admin stats view model type.
48
+ """
49
+
50
+ column_list = ['item', 'value', 'comment']
51
+
52
+
53
+ class ServerAdmin(ServerBase, Singleton):
54
+ """
55
+ Server admin type, singleton mode.
56
+ """
57
+
58
+
59
+ def __init__(self, server: 'rserver.Server') -> None:
60
+ """
61
+ Build instance attributes.
62
+
63
+ Parameters
64
+ ----------
65
+ server : Server.
66
+ """
67
+
68
+ # Build.
69
+ self.server = server
70
+
71
+ ## Admin.
72
+ self.api_admin_binds: dict[ServerAdminModel, DatabaseEngineAsync] = {}
73
+ 'Admin API model bind database engine dictionary.'
74
+ Session = async_sessionmaker()
75
+ Session.configure(binds=self.api_admin_binds)
76
+ self.admin = Admin(self.server.app, session_maker=Session)
77
+
78
+
79
+ def add_model(
80
+ self,
81
+ model: Type[ServerAdminModel] | type[rorm.Model],
82
+ engine: DatabaseEngineAsync | str = None,
83
+ label: str | None = None,
84
+ name: str | None = None,
85
+ column: str | Sequence[str] | None = None,
86
+ **attrs: Any
87
+ ) -> None:
88
+ """
89
+ Add admin model type.
90
+
91
+ Parameters
92
+ ----------
93
+ model : Model type.
94
+ - `type[rorm.Model]`: Define as `ServerAdminModel`.
95
+ engine : Database engine or name.
96
+ label : Admin model class label.
97
+ name : Admin model name.
98
+ column : Admin model display column names.
99
+ attrs : Other admin model attributes.
100
+ """
101
+
102
+ # Parameter.
103
+ if issubclass(model, rorm.Model):
104
+ class ServerAdminModel_(ServerAdminModel, model=model): ...
105
+ model = ServerAdminModel_
106
+ if type(engine) == str:
107
+ engine = self.server.db[engine]
108
+ if label is not None:
109
+ model.category = label
110
+ if name is not None:
111
+ model.name = model.name_plural = name
112
+ if type(column) == str:
113
+ column = [column]
114
+ if column is not None:
115
+ model.column_list = list(column)
116
+ for key, value in attrs.items():
117
+ setattr(model, key, value)
118
+
119
+ # Add.
120
+ self.api_admin_binds[model.model] = engine.engine
121
+ self.admin.add_view(model)
122
+
123
+
124
+ # Simple path.
125
+
126
+ ## Server admin model type.
127
+ View = ServerAdminModelView
128
+ ViewStats = ServerAdminModelViewStats
reyserver/rall.py CHANGED
@@ -9,6 +9,7 @@
9
9
  """
10
10
 
11
11
 
12
+ from .radmin import *
12
13
  from .rauth import *
13
14
  from .rbase import *
14
15
  from .rclient import *
reyserver/rauth.py CHANGED
@@ -11,10 +11,10 @@
11
11
 
12
12
  from typing import Any, Literal
13
13
  from datetime import datetime as Datetime
14
- from fastapi import APIRouter
14
+ from fastapi import APIRouter, Request
15
15
  from reydb import rorm, DatabaseEngine, DatabaseEngineAsync
16
16
  from reykit.rdata import encode_jwt, is_hash_bcrypt
17
- from reykit.rtime import now, time_to
17
+ from reykit.rtime import now
18
18
 
19
19
  from .rbase import ServerConfig, Bind, exit_api
20
20
 
@@ -74,7 +74,10 @@ class DatabaseORMTablePerm(rorm.Table):
74
74
  perm_id: int = rorm.Field(rorm.types_mysql.SMALLINT(unsigned=True), key_auto=True, comment='Permission ID.')
75
75
  name: str = rorm.Field(rorm.types.VARCHAR(50), not_null=True, index_u=True, comment='Permission name.')
76
76
  desc: str = rorm.Field(rorm.types.VARCHAR(500), comment='Permission description.')
77
- api: str = rorm.Field(rorm.types.VARCHAR(1000), comment='API resource path regular expression "match" pattern.')
77
+ api: str = rorm.Field(
78
+ rorm.types.VARCHAR(1000),
79
+ comment=r'API method and resource path regular expression "match" pattern, case insensitive, format is "{method} {path}" (e.g. "GET /users").'
80
+ )
78
81
 
79
82
 
80
83
  class DatabaseORMTableUserRole(rorm.Table):
@@ -197,8 +200,6 @@ def build_auth_db(engine: DatabaseEngine | DatabaseEngineAsync) -> None:
197
200
 
198
201
 
199
202
  auth_router = APIRouter()
200
- depend_auth_sess = Bind.create_depend_db('auth', 'sess')
201
- depend_auth_conn = Bind.create_depend_db('auth', 'conn')
202
203
 
203
204
 
204
205
  @auth_router.post('/sessions')
@@ -206,7 +207,7 @@ async def create_sessions(
206
207
  account: str = Bind.i.body,
207
208
  password: str = Bind.i.body,
208
209
  account_type: Literal['name', 'email', 'phone'] = Bind.Body('name'),
209
- conn: Bind.Conn = depend_auth_conn
210
+ conn: Bind.Conn = Bind.conn.auth
210
211
  ) -> dict:
211
212
  """
212
213
  Create session.
@@ -300,3 +301,6 @@ async def create_sessions(
300
301
  data = {'token': token}
301
302
 
302
303
  return data
304
+
305
+
306
+ def has_auth(request: Request) -> bool: ...
reyserver/rbase.py CHANGED
@@ -9,9 +9,9 @@
9
9
  """
10
10
 
11
11
 
12
- from typing import Literal, NoReturn
12
+ from typing import NoReturn, overload
13
13
  from http import HTTPStatus
14
- from fastapi import HTTPException, UploadFile as File
14
+ from fastapi import HTTPException, Request, UploadFile as File
15
15
  from fastapi.params import (
16
16
  Depends,
17
17
  Path,
@@ -35,6 +35,10 @@ __all__ = (
35
35
  'ServerExit',
36
36
  'ServerExitAPI',
37
37
  'exit_api',
38
+ 'ServerBindInstanceDatabaseSuper',
39
+ 'ServerBindInstanceDatabaseConnection',
40
+ 'ServerBindInstanceDatabaseSession',
41
+ 'ServerBindInstance',
38
42
  'ServerBind',
39
43
  'Bind'
40
44
  )
@@ -89,6 +93,71 @@ def exit_api(code: int = 400, text: str | None = None) -> NoReturn:
89
93
  raise ServerExitAPI(code, text)
90
94
 
91
95
 
96
+ class ServerBindInstanceDatabaseSuper(ServerBase):
97
+ """
98
+ Server API bind parameter build database instance super type.
99
+ """
100
+
101
+
102
+ def __getattr__(self, name: str) -> Depends:
103
+ """
104
+ Create dependencie instance of asynchronous database.
105
+
106
+ Parameters
107
+ ----------
108
+ name : Database engine name.
109
+ mode : Mode.
110
+ - `Literl['sess']`: Create ORM session instance.
111
+ - `Literl['conn']`: Create connection instance.
112
+
113
+ Returns
114
+ -------
115
+ Dependencie instance.
116
+ """
117
+
118
+
119
+ async def depend_func():
120
+ """
121
+ Dependencie function of asynchronous database.
122
+ """
123
+
124
+ # Parameter.
125
+ engine = ServerConfig.server.db[name]
126
+
127
+ # Context.
128
+ match self:
129
+ case ServerBindInstanceDatabaseConnection():
130
+ async with engine.connect() as conn:
131
+ yield conn
132
+ case ServerBindInstanceDatabaseSession():
133
+ async with engine.orm.session() as sess:
134
+ yield sess
135
+
136
+
137
+ # Create.
138
+ depend = Depends(depend_func)
139
+
140
+ return depend
141
+
142
+
143
+ @overload
144
+ def __getitem__(self, engine: str) -> DatabaseConnectionAsync: ...
145
+
146
+ __getitem__ = __getattr__
147
+
148
+
149
+ class ServerBindInstanceDatabaseConnection(ServerBindInstanceDatabaseSuper, Singleton):
150
+ """
151
+ Server API bind parameter build database connection instance type, singleton mode.
152
+ """
153
+
154
+
155
+ class ServerBindInstanceDatabaseSession(ServerBindInstanceDatabaseSuper, Singleton):
156
+ """
157
+ Server API bind parameter build database session instance type, singleton mode.
158
+ """
159
+
160
+
92
161
  class ServerBindInstance(ServerBase, Singleton):
93
162
  """
94
163
  Server API bind parameter build instance type.
@@ -256,48 +325,7 @@ class ServerBind(ServerBase, metaclass=StaticMeta):
256
325
  Server API bind parameter type.
257
326
  """
258
327
 
259
-
260
- def create_depend_db(database: str, mode: Literal['sess', 'conn']) -> Depends:
261
- """
262
- Create dependencie type of asynchronous database.
263
-
264
- Parameters
265
- ----------
266
- database : Database name.
267
- mode : Mode.
268
- - `Literl['sess']`: Create ORM session instance.
269
- - `Literl['conn']`: Create connection instance.
270
-
271
- Returns
272
- -------
273
- Dependencie type.
274
- """
275
-
276
-
277
- async def depend_db():
278
- """
279
- Dependencie function of asynchronous database.
280
- """
281
-
282
- # Parameter.
283
- engine = ServerConfig.server.db[database]
284
-
285
- # Context.
286
- if mode == 'sess':
287
- async with engine.orm.session() as sess:
288
- yield sess
289
- elif mode == 'conn':
290
- async with engine.connect() as conn:
291
- yield conn
292
-
293
-
294
- # Create.
295
- depend = Depends(depend_db)
296
-
297
- return depend
298
-
299
-
300
- Depend = Depends
328
+ Request = Request
301
329
  Path = Path
302
330
  Query = Query
303
331
  Header = Header
@@ -306,10 +334,13 @@ class ServerBind(ServerBase, metaclass=StaticMeta):
306
334
  Form = Form
307
335
  Forms = Forms
308
336
  File = File
337
+ Depend = Depends
309
338
  JSON = DatabaseORMModel
310
339
  Conn = DatabaseConnectionAsync
311
340
  Sess = DatabaseORMSessionAsync
312
341
  i = ServerBindInstance()
342
+ conn = ServerBindInstanceDatabaseConnection()
343
+ sess = ServerBindInstanceDatabaseSession()
313
344
 
314
345
 
315
346
  Bind = ServerBind
reyserver/rclient.py CHANGED
@@ -75,7 +75,6 @@ class ServerClient(ServerBase):
75
75
  # Request.
76
76
  response = request(url, json=json, check=True)
77
77
  response_dict = response.json()
78
- print(response_dict)
79
78
  token = response_dict['token']
80
79
 
81
80
  return token
reyserver/rfile.py CHANGED
@@ -178,14 +178,12 @@ def build_file_db(engine: DatabaseEngine | DatabaseEngineAsync) -> None:
178
178
 
179
179
 
180
180
  file_router = APIRouter()
181
- depend_file_sess = Bind.create_depend_db('file', 'sess')
182
- depend_file_conn = Bind.create_depend_db('file', 'conn')
183
181
 
184
182
 
185
183
  @file_router.get('/files/{file_id}')
186
184
  async def get_file_info(
187
185
  file_id: int = Bind.i.path,
188
- sess: Bind.Sess = depend_file_sess
186
+ sess: Bind.Sess = Bind.sess.file
189
187
  ) -> DatabaseORMTableInfo:
190
188
  """
191
189
  Get file information.
@@ -214,7 +212,7 @@ async def upload_file(
214
212
  file: Bind.File = Bind.i.forms,
215
213
  name: str = Bind.i.forms_n,
216
214
  note: str = Bind.i.forms_n,
217
- sess: Bind.Sess = depend_file_sess
215
+ sess: Bind.Sess = Bind.sess.file
218
216
  ) -> DatabaseORMTableInfo:
219
217
  """
220
218
  Upload file.
@@ -266,7 +264,7 @@ async def upload_file(
266
264
  @file_router.get('/files/{file_id}/download')
267
265
  async def download_file(
268
266
  file_id: int = Bind.i.path,
269
- conn: Bind.Conn = depend_file_conn
267
+ conn: Bind.Conn = Bind.conn.file
270
268
  ) -> FileResponse:
271
269
  """
272
270
  Download file.
reyserver/rserver.py CHANGED
@@ -9,7 +9,7 @@
9
9
  """
10
10
 
11
11
 
12
- from typing import Literal
12
+ from typing import Any, Literal, Type
13
13
  from collections.abc import Sequence, Callable, Coroutine
14
14
  from inspect import iscoroutinefunction
15
15
  from contextlib import asynccontextmanager, _AsyncGeneratorContextManager
@@ -19,11 +19,12 @@ from fastapi import FastAPI, Request
19
19
  from fastapi.staticfiles import StaticFiles
20
20
  from fastapi.middleware.gzip import GZipMiddleware
21
21
  from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
22
- from reydb import DatabaseAsync
22
+ from reydb import rorm, DatabaseAsync, DatabaseEngineAsync
23
23
  from reykit.rbase import CoroutineFunctionSimple, Singleton, throw
24
24
  from reykit.rrand import randchar
25
25
 
26
26
  from .rbase import ServerBase, ServerConfig, Bind
27
+ from . import radmin
27
28
 
28
29
 
29
30
  __all__ = (
@@ -159,7 +160,7 @@ class Server(ServerBase, Singleton):
159
160
  for task in before:
160
161
  await task()
161
162
 
162
- # Databse.
163
+ ## Databse.
163
164
  if db_warm:
164
165
  await self.db.warm_all()
165
166
 
@@ -170,7 +171,7 @@ class Server(ServerBase, Singleton):
170
171
  for task in after:
171
172
  await after()
172
173
 
173
- # Database.
174
+ ## Database.
174
175
  await self.db.dispose_all()
175
176
 
176
177
 
@@ -261,6 +262,46 @@ class Server(ServerBase, Singleton):
261
262
  setattr(self.app, key, value)
262
263
 
263
264
 
265
+ def add_api_admin(self) -> None:
266
+ """
267
+ Add admin API.
268
+ """
269
+
270
+ # Add.
271
+ self.admin = radmin.ServerAdmin(self)
272
+
273
+
274
+ def add_admin_model(
275
+ self,
276
+ model: Type['radmin.ServerAdminModel'] | type[rorm.Model],
277
+ engine: DatabaseEngineAsync | str = None,
278
+ label: str | None = None,
279
+ name: str | None = None,
280
+ column: str | Sequence[str] | None = None,
281
+ **attrs: Any
282
+ ) -> None:
283
+ """
284
+ Add admin model type.
285
+
286
+ Parameters
287
+ ----------
288
+ model : Model type.
289
+ - `type[rorm.Model]`: Define as `ServerAdminModel`.
290
+ engine : Database engine or name.
291
+ label : Admin model class label.
292
+ name : Admin model name.
293
+ column : Admin model display column names.
294
+ attrs : Other admin model attributes.
295
+ """
296
+
297
+ # Check.
298
+ if not hasattr(self, 'admin'):
299
+ return
300
+
301
+ # Add.
302
+ self.admin.add_model(model, engine, label, name, column, **attrs)
303
+
304
+
264
305
  def add_api_auth(self, key: str | None = None, sess_seconds: int = 28800) -> None:
265
306
  """
266
307
  Add authentication API.
@@ -273,13 +314,23 @@ class Server(ServerBase, Singleton):
273
314
  sess_seconds : Session valid seconds.
274
315
  """
275
316
 
276
- from .rauth import build_auth_db, auth_router
317
+ from .rauth import (
318
+ build_auth_db,
319
+ auth_router,
320
+ DatabaseORMTableUser,
321
+ DatabaseORMTableRole,
322
+ DatabaseORMTablePerm,
323
+ DatabaseORMTableUserRole,
324
+ DatabaseORMTableRolePerm
325
+ )
277
326
 
278
327
  # Parameter.
279
328
  if key is None:
280
329
  key = randchar(32)
281
330
 
282
- # Build database.
331
+ # Database.
332
+ if 'auth' not in self.db:
333
+ throw(AssertionError, self.db)
283
334
  engine = self.db.auth
284
335
  build_auth_db(engine)
285
336
 
@@ -288,6 +339,13 @@ class Server(ServerBase, Singleton):
288
339
  self.api_auth_sess_seconds = sess_seconds
289
340
  self.app.include_router(auth_router, tags=['auth'])
290
341
 
342
+ ## Admin.
343
+ self.add_admin_model(DatabaseORMTableUser, engine, category='auth', name='User', column_list=['user_id', 'name'])
344
+ self.add_admin_model(DatabaseORMTableRole, engine, category='auth', name='Role', column_list=['role_id', 'name'])
345
+ self.add_admin_model(DatabaseORMTablePerm, engine, category='auth', name='Perm', column_list=['perm_id', 'name'])
346
+ self.add_admin_model(DatabaseORMTableUserRole, engine, category='auth', name='User Role')
347
+ self.add_admin_model(DatabaseORMTableRolePerm, engine, category='auth', name='Role Perm')
348
+
291
349
 
292
350
  def add_api_file(self, file_dir: str = 'file') -> None:
293
351
  """
@@ -300,12 +358,23 @@ class Server(ServerBase, Singleton):
300
358
  prefix : File API path prefix.
301
359
  """
302
360
 
303
- from .rfile import build_file_db, file_router
361
+ from .rfile import (
362
+ build_file_db,
363
+ file_router,
364
+ DatabaseORMTableInfo,
365
+ DatabaseORMTableData
366
+ )
304
367
 
305
- # Build database.
368
+ # Database.
369
+ if 'file' not in self.db:
370
+ throw(AssertionError, self.db)
306
371
  engine = self.db.file
307
372
  build_file_db(engine)
308
373
 
309
374
  # Add.
310
375
  self.api_file_dir = file_dir
311
376
  self.app.include_router(file_router, tags=['file'])
377
+
378
+ ## Admin.
379
+ self.add_admin_model(DatabaseORMTableInfo, engine, category='file', name='Info', column_list=['file_id', 'name'])
380
+ self.add_admin_model(DatabaseORMTableData, engine, category='file', name='Data')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reyserver
3
- Version: 1.1.55
3
+ Version: 1.1.57
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>
@@ -15,10 +15,10 @@ License-File: LICENSE
15
15
  Keywords: API,async,asynchronous,backend,rey,reyxbo,server
16
16
  Requires-Python: >=3.12
17
17
  Requires-Dist: email-validator
18
- Requires-Dist: fastapi
19
- Requires-Dist: python-multipart
18
+ Requires-Dist: fastapi[standard]
20
19
  Requires-Dist: reydb
21
20
  Requires-Dist: reykit
21
+ Requires-Dist: sqladmin[full]
22
22
  Requires-Dist: uvicorn[standard]
23
23
  Description-Content-Type: text/markdown
24
24
 
@@ -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=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.57.dist-info/METADATA,sha256=xNQ5RW2H-ittdf-DEkYv5nIuWSwnX2xH_jKrIDkXp4k,1697
10
+ reyserver-1.1.57.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
11
+ reyserver-1.1.57.dist-info/licenses/LICENSE,sha256=UYLPqp7BvPiH8yEZduJqmmyEl6hlM3lKrFIefiD4rvk,1059
12
+ reyserver-1.1.57.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- reyserver/__init__.py,sha256=7GX64p7uI2eetJH9NJ-DTg-8iyQwOsGcviADFJCPxVA,373
2
- reyserver/rall.py,sha256=riyDRTUsigco_Bee1H4aZFb8IgvjnxdX9qcnVb9i9mE,270
3
- reyserver/rauth.py,sha256=Wua-4Y_MoPpW65M0KFO1xzj3t0zWDQHZU0LfaFXtMrs,11668
4
- reyserver/rbase.py,sha256=jgjAkT1kxzVKB60xfWxZbnQJsehhcTBr24AclTYjc4g,5603
5
- reyserver/rclient.py,sha256=IWZ3smyIP0_YJrfSrM8JFCr0FCtN02AyT3hp8YuSsDQ,5103
6
- reyserver/rfile.py,sha256=J3iQQi4modU98yfFyovyfeHqR1nh8GyTEz1Bvo6555A,8986
7
- reyserver/rserver.py,sha256=EDIspT0uWhiWl7uIhRDJtcxRvZG96GaVNWYik7pprz4,8561
8
- reyserver-1.1.55.dist-info/METADATA,sha256=u_iRcTcVBs-Nt-8r-CdZz7zGbRJyYu6Q1icFCfSQG-Q,1689
9
- reyserver-1.1.55.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
- reyserver-1.1.55.dist-info/licenses/LICENSE,sha256=UYLPqp7BvPiH8yEZduJqmmyEl6hlM3lKrFIefiD4rvk,1059
11
- reyserver-1.1.55.dist-info/RECORD,,