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/rfile.py ADDED
@@ -0,0 +1,316 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ @Time : 2025-10-06
6
+ @Author : Rey
7
+ @Contact : reyxbo@163.com
8
+ @Explain : File methods. Can create database used "self.build_db" function.
9
+ """
10
+
11
+
12
+ from fastapi import APIRouter
13
+ from fastapi.responses import FileResponse
14
+ from reydb import rorm, DatabaseEngine, DatabaseEngineAsync
15
+ from reykit.ros import get_md5
16
+
17
+ from .rbase import exit_api
18
+ from .rbind import Bind
19
+ from .rcache import wrap_cache
20
+
21
+
22
+ __all__ = (
23
+ 'DatabaseORMTableInfo',
24
+ 'DatabaseORMTableData',
25
+ 'build_db_file',
26
+ 'router_file'
27
+ )
28
+
29
+
30
+ class DatabaseORMTableInfo(rorm.Table):
31
+ """
32
+ Database "info" table ORM model.
33
+ """
34
+
35
+ __name__ = 'info'
36
+ __comment__ = 'File information table.'
37
+ create_time: rorm.Datetime = rorm.Field(field_default=':time', not_null=True, index_n=True, comment='Record create time.')
38
+ file_id: int = rorm.Field(key_auto=True, comment='File ID.')
39
+ md5: str = rorm.Field(rorm.types.CHAR(32), not_null=True, index_n=True, comment='File MD5.')
40
+ name: str = rorm.Field(rorm.types.VARCHAR(260), index_n=True, comment='File name.')
41
+ note: str = rorm.Field(rorm.types.VARCHAR(500), comment='File note.')
42
+
43
+
44
+ class DatabaseORMTableData(rorm.Table):
45
+ """
46
+ Database "data" table ORM model.
47
+ """
48
+
49
+ __name__ = 'data'
50
+ __comment__ = 'File data table.'
51
+ md5: str = rorm.Field(rorm.types.CHAR(32), key=True, comment='File MD5.')
52
+ size: int = rorm.Field(not_null=True, comment='File bytes size.')
53
+ path: str = rorm.Field(rorm.types.VARCHAR(4095), not_null=True, comment='File disk storage relative path.')
54
+
55
+
56
+ def build_db_file(engine: DatabaseEngine | DatabaseEngineAsync) -> None:
57
+ """
58
+ Check and build "file" database tables.
59
+
60
+ Parameters
61
+ ----------
62
+ db : Database engine instance.
63
+ """
64
+
65
+ # Set parameter.
66
+
67
+ ## Table.
68
+ tables = [DatabaseORMTableInfo, DatabaseORMTableData]
69
+
70
+ ## View.
71
+ views = [
72
+ {
73
+ 'table': 'data_info',
74
+ 'select': (
75
+ 'SELECT "b"."last_time", "a"."md5", "a"."size", "b"."names", "b"."notes"\n'
76
+ 'FROM "data" AS "a"\n'
77
+ 'LEFT JOIN (\n'
78
+ ' SELECT\n'
79
+ ' "md5",\n'
80
+ ' STRING_AGG(DISTINCT "name", \' | \') AS "names",\n'
81
+ ' STRING_AGG(DISTINCT "note", \' | \') AS "notes",\n'
82
+ ' MAX("create_time") as "last_time"\n'
83
+ ' FROM (\n'
84
+ ' SELECT "create_time", "md5", "name", "note"\n'
85
+ ' FROM "info"\n'
86
+ ' ORDER BY "create_time" DESC\n'
87
+ ' ) AS "INFO"\n'
88
+ ' GROUP BY "md5"\n'
89
+ ' ORDER BY "last_time" DESC\n'
90
+ ') AS "b"\n'
91
+ 'ON "a"."md5" = "b"."md5"\n'
92
+ 'ORDER BY "last_time" DESC'
93
+ )
94
+ }
95
+ ]
96
+
97
+ ## View stats.
98
+ views_stats = [
99
+ {
100
+ 'table': 'stats',
101
+ 'items': [
102
+ {
103
+ 'name': 'count',
104
+ 'select': (
105
+ 'SELECT COUNT(1)\n'
106
+ 'FROM "info"'
107
+ ),
108
+ 'comment': 'File information count.'
109
+ },
110
+ {
111
+ 'name': 'past_day_count',
112
+ 'select': (
113
+ 'SELECT COUNT(1)\n'
114
+ 'FROM "info"\n'
115
+ 'WHERE DATE_PART(\'day\', NOW() - "create_time") = 0'
116
+ ),
117
+ 'comment': 'File information count in the past day.'
118
+ },
119
+ {
120
+ 'name': 'past_week_count',
121
+ 'select': (
122
+ 'SELECT COUNT(1)\n'
123
+ 'FROM "info"\n'
124
+ 'WHERE DATE_PART(\'day\', NOW() - "create_time") <= 6'
125
+ ),
126
+ 'comment': 'File information count in the past week.'
127
+ },
128
+ {
129
+ 'name': 'past_month_count',
130
+ 'select': (
131
+ 'SELECT COUNT(1)\n'
132
+ 'FROM "info"\n'
133
+ 'WHERE DATE_PART(\'day\', NOW() - "create_time") <= 29'
134
+ ),
135
+ 'comment': 'File information count in the past month.'
136
+ },
137
+ {
138
+ 'name': 'data_count',
139
+ 'select': (
140
+ 'SELECT COUNT(1)\n'
141
+ 'FROM "data"'
142
+ ),
143
+ 'comment': 'File data unique count.'
144
+ },
145
+ {
146
+ 'name': 'total_size',
147
+ 'select': (
148
+ 'SELECT TO_CHAR(SUM("size"), \'FM999,999,999,999,999\')\n'
149
+ 'FROM "data"'
150
+ ),
151
+ 'comment': 'File total byte size.'
152
+ },
153
+ {
154
+ 'name': 'avg_size',
155
+ 'select': (
156
+ 'SELECT TO_CHAR(ROUND(AVG("size")), \'FM999,999,999,999,999\')\n'
157
+ 'FROM "data"'
158
+ ),
159
+ 'comment': 'File average byte size.'
160
+ },
161
+ {
162
+ 'name': 'max_size',
163
+ 'select': (
164
+ 'SELECT TO_CHAR(MAX("size"), \'FM999,999,999,999,999\')\n'
165
+ 'FROM "data"'
166
+ ),
167
+ 'comment': 'File maximum byte size.'
168
+ },
169
+ {
170
+ 'name': 'last_time',
171
+ 'select': (
172
+ 'SELECT MAX("create_time")\n'
173
+ 'FROM "info"'
174
+ ),
175
+ 'comment': 'File last record create time.'
176
+ }
177
+ ]
178
+ }
179
+ ]
180
+
181
+ # Build.
182
+ engine.sync_engine.build.build(tables=tables, views=views, views_stats=views_stats, skip=True)
183
+
184
+
185
+ router_file = APIRouter()
186
+
187
+
188
+ @router_file.get('/files/{file_id}')
189
+ @wrap_cache
190
+ async def get_file_info(
191
+ file_id: int = Bind.i.path,
192
+ sess: Bind.Sess = Bind.sess.file
193
+ ) -> DatabaseORMTableInfo:
194
+ """
195
+ Get file information.
196
+
197
+ Parameters
198
+ ----------
199
+ file_id : File ID.
200
+
201
+ Returns
202
+ -------
203
+ File information.
204
+ """
205
+
206
+ # Get.
207
+ table_info = await sess.get(DatabaseORMTableInfo, file_id)
208
+
209
+ # Check.
210
+ if table_info is None:
211
+ exit_api(404)
212
+
213
+ return table_info
214
+
215
+
216
+ @router_file.post('/files')
217
+ async def upload_file(
218
+ file: Bind.File = Bind.i.forms,
219
+ name: str = Bind.i.forms_n,
220
+ note: str = Bind.i.forms_n,
221
+ sess: Bind.Sess = Bind.sess.file,
222
+ server: Bind.Server = Bind.server
223
+ ) -> DatabaseORMTableInfo:
224
+ """
225
+ Upload file.
226
+
227
+ Parameters
228
+ ----------
229
+ file : File instance.
230
+ name : File name.
231
+ note : File note.
232
+
233
+ Returns
234
+ -------
235
+ File information.
236
+ """
237
+
238
+ # Parameter.
239
+ file_store = server.api_file_store
240
+ file_bytes = await file.read()
241
+ file_md5 = get_md5(file_bytes)
242
+ file_size = len(file_bytes)
243
+
244
+ # Upload.
245
+ file_path = file_store.index(file_md5)
246
+
247
+ ## Data.
248
+ if file_path is None:
249
+ file_path = file_store.store(file_bytes)
250
+ file_relpath = file_store.get_relpath(file_path)
251
+ table_data = DatabaseORMTableData(
252
+ md5=file_md5,
253
+ size=file_size,
254
+ path=file_relpath
255
+ )
256
+ await sess.add(table_data)
257
+
258
+ ## Information.
259
+ table_info = DatabaseORMTableInfo(
260
+ md5=file_md5,
261
+ name=name,
262
+ note=note
263
+ )
264
+ await sess.add(table_info)
265
+
266
+ # Get ID.
267
+ await sess.flush()
268
+
269
+ return table_info
270
+
271
+
272
+ @router_file.get('/files/{file_id}/download')
273
+ async def download_file(
274
+ file_id: int = Bind.i.path,
275
+ conn: Bind.Conn = Bind.conn.file,
276
+ server: Bind.Server = Bind.server
277
+ ) -> FileResponse:
278
+ """
279
+ Download file.
280
+
281
+ Parameters
282
+ ----------
283
+ file_id : File ID.
284
+
285
+ Returns
286
+ -------
287
+ File data.
288
+ """
289
+
290
+ # Parameter.
291
+ file_store = server.api_file_store
292
+
293
+ # Search.
294
+ sql = (
295
+ 'SELECT "name", (\n'
296
+ ' SELECT "path"\n'
297
+ ' FROM "data"\n'
298
+ ' WHERE "md5" = "info"."md5"\n'
299
+ ' LIMIT 1\n'
300
+ ') AS "path"\n'
301
+ 'FROM "info"\n'
302
+ 'WHERE "file_id" = :file_id\n'
303
+ 'LIMIT 1'
304
+ )
305
+ result = await conn.execute(sql, file_id=file_id)
306
+
307
+ # Check.
308
+ if result.empty:
309
+ exit_api(404)
310
+
311
+ # Response.
312
+ file_name, file_relpath = result.first()
313
+ file_abspath = file_store.get_abspath(file_relpath)
314
+ response = FileResponse(file_abspath, filename=file_name)
315
+
316
+ return response
reyserver/rpublic.py ADDED
@@ -0,0 +1,62 @@
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 : Public methods.
9
+ """
10
+
11
+
12
+ from fastapi import APIRouter
13
+ from fastapi.responses import HTMLResponse, FileResponse
14
+ from reykit.ros import File, Folder
15
+
16
+ from .rbind import Bind
17
+
18
+
19
+ __all__ = (
20
+ 'router_public',
21
+ )
22
+
23
+
24
+ router_public = APIRouter()
25
+
26
+
27
+ @router_public.get('/')
28
+ def home(server: Bind.Server = Bind.server) -> HTMLResponse:
29
+ """
30
+ Home page.
31
+
32
+ Parameters
33
+ ----------
34
+ Home page HTML content.
35
+ """
36
+
37
+ # Parameter.
38
+ public_dir = server.api_public_dir
39
+ file_path = Folder(public_dir) + 'index.html'
40
+ file = File(file_path)
41
+
42
+ # Response.
43
+ response = HTMLResponse(file.str)
44
+
45
+ return response
46
+
47
+
48
+ @router_public.get('/public/{path:path}')
49
+ def download_public_file(path: str = Bind.i.path) -> FileResponse:
50
+ """
51
+ Download public file.
52
+
53
+ Parameters
54
+ ----------
55
+ path : Relative path of based on public directory.
56
+
57
+ Returns
58
+ -------
59
+ File.
60
+ """
61
+
62
+ ...
reyserver/rredirect.py ADDED
@@ -0,0 +1,48 @@
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 : Redirect methods.
9
+ """
10
+
11
+
12
+ from fastapi import APIRouter
13
+ from fastapi.responses import RedirectResponse
14
+ from reykit.rnet import join_url
15
+
16
+ from .rbind import Bind
17
+
18
+
19
+ __all__ = (
20
+ 'router_redirect',
21
+ )
22
+
23
+
24
+ router_redirect = APIRouter()
25
+
26
+
27
+ @router_redirect.get('/{path:path}')
28
+ def redirect_all(
29
+ path: str = Bind.i.path,
30
+ server: Bind.Server = Bind.server
31
+ ) -> RedirectResponse:
32
+ """
33
+ Redirect all requests to the target server.
34
+
35
+ Parameters
36
+ ----------
37
+ path : Resource path.
38
+
39
+ Returns
40
+ -------
41
+ Redirect response.
42
+ """
43
+
44
+ # Response.
45
+ url = join_url(server.api_redirect_server_url, path)
46
+ response = RedirectResponse(url, 308)
47
+
48
+ return response