reyserver 1.1.93__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/rbind.py ADDED
@@ -0,0 +1,335 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ @Time : 2025-10-21
6
+ @Author : Rey
7
+ @Contact : reyxbo@163.com
8
+ @Explain : Dependency bind methods.
9
+ """
10
+
11
+
12
+ from typing import overload, TYPE_CHECKING
13
+ from fastapi import FastAPI, Request, UploadFile
14
+ from fastapi.params import (
15
+ Depends,
16
+ Path,
17
+ Query,
18
+ Header,
19
+ Cookie,
20
+ Body,
21
+ Form,
22
+ File as Forms
23
+ )
24
+ from reydb.rconn import DatabaseConnectionAsync
25
+ from reydb.rorm import DatabaseORMSessionAsync
26
+ from reykit.rbase import StaticMeta, Singleton, throw
27
+
28
+ from . import rserver
29
+ from .rbase import ServerBase, depend_pass
30
+
31
+
32
+ __all__ = (
33
+ 'ServerBindInstanceDatabaseSuper',
34
+ 'ServerBindInstanceDatabaseConnection',
35
+ 'ServerBindInstanceDatabaseSession',
36
+ 'ServerBindInstance',
37
+ 'ServerBind',
38
+ 'Bind'
39
+ )
40
+
41
+
42
+ class ServerBindInstanceDatabaseSuper(ServerBase):
43
+ """
44
+ Server API bind parameter build database instance super type.
45
+ """
46
+
47
+
48
+ def __getattr__(self, name: str) -> Depends:
49
+ """
50
+ Create dependencie instance of asynchronous database.
51
+
52
+ Parameters
53
+ ----------
54
+ name : Database engine name.
55
+ mode : Mode.
56
+ - `Literl['sess']`: Create ORM session instance.
57
+ - `Literl['conn']`: Create connection instance.
58
+
59
+ Returns
60
+ -------
61
+ Dependencie instance.
62
+ """
63
+
64
+
65
+ async def depend_func(server: Bind.Server = Bind.server):
66
+ """
67
+ Dependencie function of asynchronous database.
68
+ """
69
+
70
+ # Check.
71
+ if server.db is None:
72
+ throw(TypeError, server.db)
73
+
74
+ # Parameter.
75
+ engine = server.db[name]
76
+
77
+ # Context.
78
+ match self:
79
+ case ServerBindInstanceDatabaseConnection():
80
+ async with engine.connect() as conn:
81
+ yield conn
82
+ case ServerBindInstanceDatabaseSession():
83
+ async with engine.orm.session() as sess:
84
+ yield sess
85
+
86
+
87
+ # Create.
88
+ depend = Depends(depend_func)
89
+
90
+ return depend
91
+
92
+
93
+ @overload
94
+ def __getitem__(self, engine: str) -> DatabaseConnectionAsync: ...
95
+
96
+ __getitem__ = __getattr__
97
+
98
+
99
+ class ServerBindInstanceDatabaseConnection(ServerBindInstanceDatabaseSuper, Singleton):
100
+ """
101
+ Server API bind parameter build database connection instance type, singleton mode.
102
+ """
103
+
104
+
105
+ class ServerBindInstanceDatabaseSession(ServerBindInstanceDatabaseSuper, Singleton):
106
+ """
107
+ Server API bind parameter build database session instance type, singleton mode.
108
+ """
109
+
110
+
111
+ class ServerBindInstance(ServerBase, Singleton):
112
+ """
113
+ Server API bind parameter build instance type.
114
+ """
115
+
116
+
117
+ @property
118
+ def path(self) -> Path:
119
+ """
120
+ Path instance.
121
+ """
122
+
123
+ # Build.
124
+ path = Path()
125
+
126
+ return path
127
+
128
+
129
+ @property
130
+ def query(self) -> Query:
131
+ """
132
+ Query instance.
133
+ """
134
+
135
+ # Build.
136
+ query = Query()
137
+
138
+ return query
139
+
140
+
141
+ @property
142
+ def header(self) -> Header:
143
+ """
144
+ Header instance.
145
+ """
146
+
147
+ # Build.
148
+ header = Header()
149
+
150
+ return header
151
+
152
+
153
+ @property
154
+ def cookie(self) -> Cookie:
155
+ """
156
+ Cookie instance.
157
+ """
158
+
159
+ # Build.
160
+ cookie = Cookie()
161
+
162
+ return cookie
163
+
164
+
165
+ @property
166
+ def body(self) -> Body:
167
+ """
168
+ Body instance.
169
+ """
170
+
171
+ # Build.
172
+ body = Body()
173
+
174
+ return body
175
+
176
+
177
+ @property
178
+ def form(self) -> Form:
179
+ """
180
+ Form instance.
181
+ """
182
+
183
+ # Build.
184
+ form = Form()
185
+
186
+ return form
187
+
188
+
189
+ @property
190
+ def forms(self) -> Forms:
191
+ """
192
+ Forms instance.
193
+ """
194
+
195
+ # Build.
196
+ forms = Forms()
197
+
198
+ return forms
199
+
200
+
201
+ @property
202
+ def query_n(self) -> Query:
203
+ """
204
+ Query instance, default `None`.
205
+ """
206
+
207
+ # Build.
208
+ query = Query(None)
209
+
210
+ return query
211
+
212
+
213
+ @property
214
+ def header_n(self) -> Header:
215
+ """
216
+ Header instance, default `None`.
217
+ """
218
+
219
+ # Build.
220
+ header = Header(None)
221
+
222
+ return header
223
+
224
+
225
+ @property
226
+ def cookie_n(self) -> Cookie:
227
+ """
228
+ Cookie instance, default `None`.
229
+ """
230
+
231
+ # Build.
232
+ cookie = Cookie(None)
233
+
234
+ return cookie
235
+
236
+
237
+ @property
238
+ def body_n(self) -> Body:
239
+ """
240
+ Body instance, default `None`.
241
+ """
242
+
243
+ # Build.
244
+ body = Body(None)
245
+
246
+ return body
247
+
248
+
249
+ @property
250
+ def form_n(self) -> Form:
251
+ """
252
+ Form instance, default `None`.
253
+ """
254
+
255
+ # Build.
256
+ form = Form(None)
257
+
258
+ return form
259
+
260
+
261
+ @property
262
+ def forms_n(self) -> Forms:
263
+ """
264
+ Forms instance, default `None`.
265
+ """
266
+
267
+ # Build.
268
+ forms = Forms(None)
269
+
270
+ return forms
271
+
272
+
273
+ async def depend_server(request: Request) -> 'rserver.Server':
274
+ """
275
+ Dependencie function of now Server instance.
276
+
277
+ Parameters
278
+ ----------
279
+ request : Request.
280
+
281
+ Returns
282
+ -------
283
+ Server.
284
+ """
285
+
286
+ # Get.
287
+ app: FastAPI = request.app
288
+ server: rserver.Server = app.extra['server']
289
+
290
+ return server
291
+
292
+
293
+ class ServerBind(ServerBase, metaclass=StaticMeta):
294
+ """
295
+ Server API bind parameter type.
296
+ """
297
+
298
+ Request = Request
299
+ 'Reqeust instance dependency type.'
300
+ Path = Path
301
+ 'URL source path dependency type.'
302
+ Query = Query
303
+ 'URL query parameter dependency type.'
304
+ Header = Header
305
+ 'Request header parameter dependency type.'
306
+ Cookie = Cookie
307
+ 'Request header cookie parameter dependency type.'
308
+ Body = Body
309
+ 'Request body JSON parameter dependency type.'
310
+ Form = Form
311
+ 'Request body form parameter dependency type.'
312
+ Forms = Forms
313
+ 'Request body multiple forms parameter dependency type.'
314
+ File = UploadFile
315
+ 'Type hints file type.'
316
+ Depend = Depends
317
+ 'Dependency type.'
318
+ Conn = DatabaseConnectionAsync
319
+ Sess = DatabaseORMSessionAsync
320
+ if TYPE_CHECKING:
321
+ Server = rserver.Server
322
+ 'Server type.'
323
+ server: Depend = Depend(depend_server)
324
+ 'Server instance dependency type.'
325
+ i = ServerBindInstance()
326
+ 'Server API bind parameter build instance.'
327
+ conn = ServerBindInstanceDatabaseConnection()
328
+ 'Server API bind parameter asynchronous database connection.'
329
+ sess = ServerBindInstanceDatabaseSession()
330
+ 'Server API bind parameter asynchronous database session.'
331
+ token: Depend = depend_pass
332
+ 'Server authentication token dependency type.'
333
+
334
+
335
+ Bind = ServerBind
reyserver/rcache.py ADDED
@@ -0,0 +1,116 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ @Time : 2025-10-25
6
+ @Author : Rey
7
+ @Contact : reyxbo@163.com
8
+ @Explain : Cache methods.
9
+ """
10
+
11
+
12
+ from typing import Any
13
+ from collections.abc import Callable
14
+ from inspect import iscoroutinefunction
15
+ from fastapi import Request, Response
16
+ from fastapi_cache import FastAPICache
17
+ from fastapi_cache.coder import PickleCoder
18
+ from fastapi_cache.backends.redis import RedisBackend
19
+ from fastapi_cache.decorator import cache as fastapi_cache_cache
20
+ from redis.asyncio import Redis
21
+ from reykit.rbase import CallableT
22
+ from reykit.ros import get_md5
23
+ from reykit.rre import sub
24
+
25
+
26
+ __all__ = (
27
+ 'init_cache',
28
+ 'wrap_cache'
29
+ )
30
+
31
+
32
+ def init_cache(redis: Redis, redis_expire: int | None = None) -> None:
33
+ """
34
+ Initialize cache based on Redis.
35
+
36
+ Parameters
37
+ ----------
38
+ redis : Asynchronous Redis
39
+ redis_expire : Redis cache expire seconds.
40
+ """
41
+
42
+
43
+ def key_builder(
44
+ func: Callable,
45
+ namespace: str,
46
+ request: Request | None,
47
+ response: Response | None,
48
+ args: tuple,
49
+ kwargs: dict[str, Any],
50
+ ) -> str:
51
+ """
52
+ Cache key builder.
53
+
54
+ Parameters
55
+ ----------
56
+ func : Decorated function.
57
+ namespace : Cache key prefix.
58
+ request : API Request.
59
+ response : API response.
60
+ args : Position arguments of decorated function.
61
+ kwargs : Keyword arguments of decorated function.
62
+ """
63
+
64
+ # Parameter.
65
+ data = f'{func.__module__}:{func.__name__}:{args}:{kwargs}'
66
+ pattern = r' object at 0x[0-9a-fA-F]+>'
67
+ data = sub(pattern, data, '>')
68
+
69
+ # Build.
70
+ key = get_md5(data)
71
+
72
+ return key
73
+
74
+
75
+ # Initialize.
76
+ backend = RedisBackend(redis)
77
+ FastAPICache.init(
78
+ backend,
79
+ expire=redis_expire,
80
+ coder=PickleCoder,
81
+ key_builder=key_builder
82
+ )
83
+
84
+
85
+ def wrap_cache(func_or_expire: CallableT | int | None = None) -> CallableT | Callable[[CallableT], CallableT]:
86
+ """
87
+ Decorator, use Redis cache.
88
+ When Redis is not set, then skip.
89
+
90
+ Parameters
91
+ ----------
92
+ func_or_expire : Decorated function or Redis cache expire seconds.
93
+
94
+ Returns
95
+ -------
96
+ Decorated function or decorator.
97
+
98
+ Examples
99
+ --------
100
+ No parameter.
101
+ >>> @wrap_cache
102
+ >>> def foo(): ...
103
+
104
+ Set parameter.
105
+ >>> @wrap_cache(60)
106
+ >>> def foo(): ...
107
+ """
108
+
109
+ # Decorate.
110
+ if callable(func_or_expire):
111
+ decorator = fastapi_cache_cache()
112
+ func = decorator(func_or_expire)
113
+ return func
114
+ else:
115
+ decorator = fastapi_cache_cache(func_or_expire)
116
+ return decorator
reyserver/rclient.py ADDED
@@ -0,0 +1,267 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ @Time : 2025-10-09
6
+ @Author : Rey
7
+ @Contact : reyxbo@163.com
8
+ @Explain : Client methods.
9
+ """
10
+
11
+
12
+ from typing import TypedDict
13
+ from datetime import datetime as Datetime
14
+ from requests import Response
15
+ from reykit.rbase import copy_type_hints
16
+ from reykit.ros import File, Folder, overload
17
+ from reykit.rnet import join_url, request, get_response_file_name
18
+
19
+ from .rbase import ServerBase
20
+
21
+
22
+ __all__ = (
23
+ 'ServerClient',
24
+ )
25
+
26
+
27
+ FileInfo = TypedDict('FileInfo', {'create_time': Datetime, 'md5': str, 'name': str | None, 'size': int, 'note': str | None})
28
+
29
+
30
+ class ServerClient(ServerBase):
31
+ """
32
+ Server client type.
33
+ """
34
+
35
+
36
+ def __init__(
37
+ self,
38
+ username: str,
39
+ password: str,
40
+ url: str = 'http://127.0.0.1:8000',
41
+ ) -> None:
42
+ """
43
+ Build instance attributes.
44
+
45
+ Parameters
46
+ ----------
47
+ username: User name.
48
+ password: User password.
49
+ url : Server url.
50
+ """
51
+
52
+ # Build.
53
+ self.username = username
54
+ self.password = password
55
+ self.url = url
56
+ self.token = self.get_token(username, password)
57
+ self.request = copy_type_hints(self._request, request)
58
+
59
+
60
+ def get_token(
61
+ self,
62
+ username: str,
63
+ password: str
64
+ ) -> str:
65
+ """
66
+ Get token.
67
+
68
+ Parameters
69
+ ----------
70
+ username : User name.
71
+ password : User password.
72
+
73
+ Returns
74
+ -------
75
+ Token.
76
+ """
77
+
78
+ # Parameter.
79
+ url = join_url(self.url, 'token')
80
+ data = {
81
+ 'username': username,
82
+ 'password': password
83
+ }
84
+
85
+ # Request.
86
+ response = request(url, data=data, check=True)
87
+ response_dict = response.json()
88
+ token = response_dict['access_token']
89
+
90
+ return token
91
+
92
+
93
+ def _request(self, *args, **kwargs) -> Response:
94
+ """
95
+ Send request.
96
+
97
+ Parameters
98
+ ----------
99
+ args : Position arguments.
100
+ kwargs : Keyword arguments.
101
+
102
+ Returns
103
+ -------
104
+ Response.
105
+ """
106
+
107
+ # Parameter.
108
+ headers = kwargs.setdefault('headers', {})
109
+ headers['Authorization'] = f'Bearer {self.token}'
110
+ kwargs['check'] = list(range(200, 400))
111
+ kwargs['check'].append(401)
112
+
113
+ # Request.
114
+ response = request(*args, **kwargs)
115
+
116
+ # Check.
117
+ if response.status_code != 401:
118
+ return response
119
+
120
+ # Try request.
121
+ self.token = self.get_token(self.username, self.password)
122
+ headers['Authorization'] = f'Bearer {self.token}'
123
+ kwargs['check'] = True
124
+ response = request(*args, **kwargs)
125
+
126
+ return response
127
+
128
+
129
+ def upload_file(
130
+ self,
131
+ source: str | bytes,
132
+ name: str | None = None,
133
+ note: str | None = None
134
+ ) -> int:
135
+ """
136
+ Upload file.
137
+
138
+ Parameters
139
+ ----------
140
+ source : File path or file bytes.
141
+ name : File name.
142
+ - `None`: Automatic set.
143
+ `parameter 'file' is 'str'`: Use path file name.
144
+ `parameter 'file' is 'bytes'`: No name.
145
+ - `str`: Use this name.
146
+ note : File note.
147
+
148
+ Returns
149
+ -------
150
+ File ID.
151
+ """
152
+
153
+ # Parameter.
154
+ url = join_url(self.url, 'files')
155
+ match source:
156
+
157
+ ## File path.
158
+ case str():
159
+ file = File(source)
160
+ file_bytes = file.bytes
161
+ file_name = file.name_suffix
162
+
163
+ ## File bytes.
164
+ case bytes() | bytearray():
165
+ if type(source) == bytearray:
166
+ source = bytes(source)
167
+ file_bytes = source
168
+ file_name = None
169
+
170
+ ## File name.
171
+ if name is not None:
172
+ file_name = name
173
+
174
+ # Request.
175
+ data = {'name': file_name, 'note': note}
176
+ files = {'file': file_bytes}
177
+ response = self.request(url, data=data, files=files, check=True)
178
+
179
+ ## Extract.
180
+ response_json = response.json()
181
+ file_id = response_json['file_id']
182
+
183
+ return file_id
184
+
185
+
186
+ @overload
187
+ def download_file(
188
+ self,
189
+ file_id: int,
190
+ path: None = None
191
+ ) -> bytes: ...
192
+
193
+ @overload
194
+ def download_file(
195
+ self,
196
+ file_id: int,
197
+ path: str
198
+ ) -> str: ...
199
+
200
+ def download_file(
201
+ self,
202
+ file_id: int,
203
+ path: str | None = None
204
+ ) -> bytes | str:
205
+ """
206
+ Download file.
207
+
208
+ Parameters
209
+ ----------
210
+ file_id : File ID.
211
+ path : File save path.
212
+ - `None`: Not save and return file bytes.
213
+ - `str`: Save and return file path.
214
+ `File path`: Use this file path.
215
+ `Folder path`: Use this folder path and original name.
216
+
217
+ Returns
218
+ -------
219
+ File bytes or file path.
220
+ """
221
+
222
+ # Parameter.
223
+ url = join_url(self.url, 'files', file_id, 'download')
224
+
225
+ # Request.
226
+ response = self.request(url, check=True)
227
+ file_bytes = response.content
228
+
229
+ # Not save.
230
+ if path is None:
231
+ return file_bytes
232
+
233
+ # Save.
234
+ else:
235
+ folder = Folder(path)
236
+ if folder:
237
+ file_name = get_response_file_name(response)
238
+ path = folder + file_name
239
+ file = File(path)
240
+ file(file_bytes)
241
+ return file.path
242
+
243
+
244
+ def get_file_info(
245
+ self,
246
+ file_id: int
247
+ ) -> FileInfo:
248
+ """
249
+ Query file information.
250
+
251
+ Parameters
252
+ ----------
253
+ file_id : File ID.
254
+
255
+ Returns
256
+ -------
257
+ File information.
258
+ """
259
+
260
+ # Parameter.
261
+ url = join_url(self.url, 'files', file_id)
262
+
263
+ # Request.
264
+ response = self.request(url, check=True)
265
+ response_dict = response.json()
266
+
267
+ return response_dict