reyserver 1.1.44__py3-none-any.whl → 1.1.46__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/rbase.py +56 -12
- reyserver/rclient.py +58 -1
- reyserver/rfile.py +116 -60
- reyserver/rserver.py +2 -2
- {reyserver-1.1.44.dist-info → reyserver-1.1.46.dist-info}/METADATA +1 -1
- reyserver-1.1.46.dist-info/RECORD +10 -0
- reyserver-1.1.44.dist-info/RECORD +0 -10
- {reyserver-1.1.44.dist-info → reyserver-1.1.46.dist-info}/WHEEL +0 -0
- {reyserver-1.1.44.dist-info → reyserver-1.1.46.dist-info}/licenses/LICENSE +0 -0
reyserver/rbase.py
CHANGED
@@ -9,19 +9,21 @@
|
|
9
9
|
"""
|
10
10
|
|
11
11
|
|
12
|
-
from typing import Sequence
|
12
|
+
from typing import Sequence, Literal
|
13
13
|
from inspect import iscoroutinefunction
|
14
14
|
from contextlib import asynccontextmanager
|
15
|
-
from fastapi import FastAPI
|
16
|
-
from
|
17
|
-
from reykit.rbase import CoroutineFunctionSimple, Base, ConfigMeta, throw
|
15
|
+
from fastapi import FastAPI, HTTPException, status
|
16
|
+
from reykit.rbase import CoroutineFunctionSimple, Base, ConfigMeta, Exit
|
18
17
|
|
19
|
-
import rserver
|
18
|
+
from . import rserver
|
20
19
|
|
21
20
|
|
22
21
|
__all__ = (
|
23
22
|
'ServerBase',
|
24
23
|
'ServerConfig',
|
24
|
+
'ServerExit',
|
25
|
+
'ServerExitHTTP',
|
26
|
+
'ServerExitHTTP404',
|
25
27
|
'create_lifespan',
|
26
28
|
'create_depend_sess'
|
27
29
|
)
|
@@ -42,6 +44,41 @@ class ServerConfig(ServerBase, metaclass=ConfigMeta):
|
|
42
44
|
'Server instance.'
|
43
45
|
|
44
46
|
|
47
|
+
class ServerExit(ServerBase, Exit):
|
48
|
+
"""
|
49
|
+
Server exit type.
|
50
|
+
"""
|
51
|
+
|
52
|
+
|
53
|
+
class ServerExitHTTP(ServerExit, HTTPException):
|
54
|
+
"""
|
55
|
+
Server HTTP exit type.
|
56
|
+
"""
|
57
|
+
|
58
|
+
status_code: int
|
59
|
+
|
60
|
+
|
61
|
+
def __init__(self, text: str | None = None):
|
62
|
+
"""
|
63
|
+
Build instance attributes.
|
64
|
+
|
65
|
+
Parameters
|
66
|
+
----------
|
67
|
+
text : Explain text.
|
68
|
+
"""
|
69
|
+
|
70
|
+
# Super.
|
71
|
+
super().__init__(self.status_code, text)
|
72
|
+
|
73
|
+
|
74
|
+
class ServerExitHTTP404(ServerExitHTTP):
|
75
|
+
"""
|
76
|
+
Server HTTP 404 exit type.
|
77
|
+
"""
|
78
|
+
|
79
|
+
status_code = status.HTTP_404_NOT_FOUND
|
80
|
+
|
81
|
+
|
45
82
|
def create_lifespan(
|
46
83
|
before: CoroutineFunctionSimple | Sequence[CoroutineFunctionSimple] | None = None,
|
47
84
|
after: CoroutineFunctionSimple | Sequence[CoroutineFunctionSimple] | None = None,
|
@@ -89,13 +126,16 @@ def create_lifespan(
|
|
89
126
|
return lifespan
|
90
127
|
|
91
128
|
|
92
|
-
def
|
129
|
+
def create_depend_db(database: str, mode: Literal['sess', 'conn']):
|
93
130
|
"""
|
94
|
-
Create dependencie function of asynchronous database
|
131
|
+
Create dependencie function of asynchronous database.
|
95
132
|
|
96
133
|
Parameters
|
97
134
|
----------
|
98
135
|
database : Database name.
|
136
|
+
mode : Mode.
|
137
|
+
- `Literl['sess']`: Create ORM session instance.
|
138
|
+
- `Literl['conn']`: Create connection instance.
|
99
139
|
|
100
140
|
Returns
|
101
141
|
-------
|
@@ -103,17 +143,21 @@ def create_depend_sess(database: str):
|
|
103
143
|
"""
|
104
144
|
|
105
145
|
|
106
|
-
async def
|
146
|
+
async def depend():
|
107
147
|
"""
|
108
|
-
Dependencie function of asynchronous database
|
148
|
+
Dependencie function of asynchronous database.
|
109
149
|
"""
|
110
150
|
|
111
151
|
# Parameter.
|
112
152
|
engine = ServerConfig.server.db[database]
|
113
153
|
|
114
154
|
# Context.
|
115
|
-
|
116
|
-
|
155
|
+
if mode == 'sess':
|
156
|
+
async with engine.orm.session() as sess:
|
157
|
+
yield sess
|
158
|
+
elif mode == 'conn':
|
159
|
+
async with engine.connect() as conn:
|
160
|
+
yield conn
|
117
161
|
|
118
162
|
|
119
|
-
return
|
163
|
+
return depend
|
reyserver/rclient.py
CHANGED
@@ -9,6 +9,9 @@
|
|
9
9
|
"""
|
10
10
|
|
11
11
|
|
12
|
+
from reykit.ros import File, get_md5
|
13
|
+
from reykit.rnet import join_url, request
|
14
|
+
|
12
15
|
from .rbase import ServerBase
|
13
16
|
|
14
17
|
|
@@ -36,7 +39,61 @@ class ServerClient(ServerBase):
|
|
36
39
|
self.url = url
|
37
40
|
|
38
41
|
|
39
|
-
def upload_file(
|
42
|
+
def upload_file(
|
43
|
+
self,
|
44
|
+
source: str | bytes,
|
45
|
+
name: str | None = None,
|
46
|
+
note: str | None = None
|
47
|
+
) -> int:
|
48
|
+
"""
|
49
|
+
Upload file.
|
50
|
+
|
51
|
+
Parameters
|
52
|
+
----------
|
53
|
+
source : File path or file bytes.
|
54
|
+
name : File name.
|
55
|
+
- `None`: Automatic set.
|
56
|
+
`parameter 'file' is 'str'`: Use path file name.
|
57
|
+
`parameter 'file' is 'bytes'`: No name.
|
58
|
+
- `str`: Use this name.
|
59
|
+
note : File note.
|
60
|
+
|
61
|
+
Returns
|
62
|
+
-------
|
63
|
+
File ID.
|
64
|
+
"""
|
65
|
+
|
66
|
+
# Handle parameter.
|
67
|
+
url = join_url(self.url, 'file', 'upload')
|
68
|
+
match source:
|
69
|
+
|
70
|
+
## File path.
|
71
|
+
case str():
|
72
|
+
file = File(source)
|
73
|
+
file_bytes = file.bytes
|
74
|
+
file_name = file.name_suffix
|
75
|
+
|
76
|
+
## File bytes.
|
77
|
+
case bytes() | bytearray():
|
78
|
+
if type(source) == bytearray:
|
79
|
+
source = bytes(source)
|
80
|
+
file_bytes = source
|
81
|
+
file_name = None
|
82
|
+
|
83
|
+
## File name.
|
84
|
+
if name is not None:
|
85
|
+
file_name = name
|
86
|
+
|
87
|
+
# Upload.
|
88
|
+
data = {'name': file_name, 'note': note}
|
89
|
+
files = {'file': file_bytes}
|
90
|
+
response = request(url, data=data, files=files, check=True)
|
91
|
+
|
92
|
+
## Extract.
|
93
|
+
response_json = response.json()
|
94
|
+
file_id = response_json['file_id']
|
95
|
+
|
96
|
+
return file_id
|
40
97
|
|
41
98
|
|
42
99
|
def download_file(self): ...
|
reyserver/rfile.py
CHANGED
@@ -9,18 +9,28 @@
|
|
9
9
|
"""
|
10
10
|
|
11
11
|
|
12
|
-
from
|
13
|
-
|
12
|
+
from fastapi import (
|
13
|
+
APIRouter,
|
14
|
+
Depends,
|
15
|
+
Path,
|
16
|
+
Form,
|
17
|
+
File,
|
18
|
+
UploadFile
|
19
|
+
)
|
20
|
+
from fastapi.responses import FileResponse
|
14
21
|
from reydb import rorm
|
22
|
+
from reydb.rorm import DatabaseORMSessionAsync
|
23
|
+
from reydb.rconn import DatabaseConnectionAsync
|
15
24
|
from reykit.ros import FileStore, get_md5
|
16
25
|
|
17
|
-
from .rbase import ServerConfig,
|
26
|
+
from .rbase import ServerConfig, ServerExitHTTP404, create_depend_db
|
18
27
|
|
19
28
|
|
20
29
|
__all__ = (
|
21
30
|
'DatabaseORMTableInfo',
|
22
31
|
'DatabaseORMTableData',
|
23
|
-
'build_file_db'
|
32
|
+
'build_file_db',
|
33
|
+
'file_router'
|
24
34
|
)
|
25
35
|
|
26
36
|
|
@@ -50,62 +60,7 @@ class DatabaseORMTableData(rorm.Model, table=True):
|
|
50
60
|
path: str = rorm.Field(rorm.types.VARCHAR(4095), not_null=True, comment='File disk storage path.')
|
51
61
|
|
52
62
|
|
53
|
-
|
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
|
-
note: Annotated[str, Form()],
|
61
|
-
sess: Annotated[rorm.DatabaseORMSessionAsync, Depends(depend_sess_file)]
|
62
|
-
):
|
63
|
-
"""
|
64
|
-
Upload file.
|
65
|
-
|
66
|
-
Parameters
|
67
|
-
----------
|
68
|
-
file : File instance.
|
69
|
-
note : File note.
|
70
|
-
sess : Asynchronous database session.
|
71
|
-
"""
|
72
|
-
|
73
|
-
# Handle parameter.
|
74
|
-
file_store = FileStore(ServerConfig.server.api_file_dir)
|
75
|
-
file_name = file.filename
|
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=file_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
|
-
def build_file_db(self) -> None:
|
63
|
+
def build_file_db() -> None:
|
109
64
|
"""
|
110
65
|
Check and build `file` database tables.
|
111
66
|
"""
|
@@ -226,3 +181,104 @@ def build_file_db(self) -> None:
|
|
226
181
|
|
227
182
|
# Build.
|
228
183
|
engine.sync_engine.build.build(tables=tables, views=views, views_stats=views_stats, skip=True)
|
184
|
+
|
185
|
+
|
186
|
+
file_router = APIRouter()
|
187
|
+
depend_file_sess = create_depend_db('file', 'sess')
|
188
|
+
depend_file_conn = create_depend_db('file', 'conn')
|
189
|
+
|
190
|
+
|
191
|
+
@file_router.post('/')
|
192
|
+
async def upload_file(
|
193
|
+
file: UploadFile = File(),
|
194
|
+
name: str = Form(None),
|
195
|
+
note: str = Form(None),
|
196
|
+
sess: rorm.DatabaseORMSessionAsync = Depends(depend_file_sess)
|
197
|
+
) -> dict:
|
198
|
+
"""
|
199
|
+
Upload file.
|
200
|
+
|
201
|
+
Parameters
|
202
|
+
----------
|
203
|
+
file : File instance.
|
204
|
+
note : File note.
|
205
|
+
"""
|
206
|
+
|
207
|
+
# Handle parameter.
|
208
|
+
file_store = FileStore(ServerConfig.server.api_file_dir)
|
209
|
+
file_bytes = await file.read()
|
210
|
+
file_md5 = get_md5(file_bytes)
|
211
|
+
file_size = len(file_bytes)
|
212
|
+
|
213
|
+
# Upload.
|
214
|
+
file_path = file_store.index(file_md5)
|
215
|
+
|
216
|
+
## Data.
|
217
|
+
if file_path is None:
|
218
|
+
file_path = file_store.store(file_bytes)
|
219
|
+
table_data = DatabaseORMTableData(
|
220
|
+
md5=file_md5,
|
221
|
+
size=file_size,
|
222
|
+
path=file_path
|
223
|
+
)
|
224
|
+
await sess.add(table_data)
|
225
|
+
|
226
|
+
## Information.
|
227
|
+
table_info = DatabaseORMTableInfo(
|
228
|
+
md5=file_md5,
|
229
|
+
name=name,
|
230
|
+
note=note
|
231
|
+
)
|
232
|
+
await sess.add(table_info)
|
233
|
+
|
234
|
+
# Get ID.
|
235
|
+
await sess.flush()
|
236
|
+
file_id = table_info.file_id
|
237
|
+
|
238
|
+
return {'file_id': file_id}
|
239
|
+
|
240
|
+
|
241
|
+
@file_router.get('/{file_id}/download')
|
242
|
+
async def download_file(
|
243
|
+
file_id: int = Path(),
|
244
|
+
conn: DatabaseConnectionAsync = Depends(depend_file_conn)
|
245
|
+
) -> FileResponse:
|
246
|
+
"""
|
247
|
+
Download file.
|
248
|
+
|
249
|
+
Parameters
|
250
|
+
----------
|
251
|
+
file_id : File ID.
|
252
|
+
"""
|
253
|
+
|
254
|
+
# Search.
|
255
|
+
sql = (
|
256
|
+
'SELECT `name`, (\n'
|
257
|
+
' SELECT `path`\n'
|
258
|
+
f' FROM `{conn.engine.database}`.`data`\n'
|
259
|
+
f' WHERE `md5` = `info`.`md5`\n'
|
260
|
+
' LIMIT 1\n'
|
261
|
+
') AS `path`\n'
|
262
|
+
f'FROM `{conn.engine.database}`.`info`\n'
|
263
|
+
'WHERE `file_id` = :file_id\n'
|
264
|
+
'LIMIT 1'
|
265
|
+
)
|
266
|
+
result = await conn.execute(sql, file_id=file_id)
|
267
|
+
|
268
|
+
# Check.
|
269
|
+
if result.empty:
|
270
|
+
text = "file ID '%s' not exist" % file_id
|
271
|
+
raise ServerExitHTTP404(text)
|
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 = Path(),
|
283
|
+
sess: DatabaseConnectionAsync = Depends(depend_file_sess)
|
284
|
+
) -> dict: ...
|
reyserver/rserver.py
CHANGED
@@ -114,7 +114,7 @@ class Server(ServerBase, Singleton):
|
|
114
114
|
return {'message': 'test'}
|
115
115
|
|
116
116
|
|
117
|
-
def add_api_file(self, file_dir: str = 'file'
|
117
|
+
def add_api_file(self, file_dir: str = 'file') -> None:
|
118
118
|
"""
|
119
119
|
Add file API.
|
120
120
|
|
@@ -131,4 +131,4 @@ class Server(ServerBase, Singleton):
|
|
131
131
|
|
132
132
|
# Add.
|
133
133
|
self.api_file_dir = file_dir
|
134
|
-
self.app.include_router(file_router, prefix=
|
134
|
+
self.app.include_router(file_router, prefix='/files', tags=['file'])
|
@@ -0,0 +1,10 @@
|
|
1
|
+
reyserver/__init__.py,sha256=JmDkcdP7oxmehE53rRRL_zhxmPinAbJRONpQp5qhsGI,340
|
2
|
+
reyserver/rall.py,sha256=6vqacf9erEfVfK5sgGJMDXD1h4ebt5gDN7NrPdtELms,248
|
3
|
+
reyserver/rbase.py,sha256=6mfTSuVzJGTK6Ch0HNlN0bmKIkPMLUlUAAE_EGt-1uo,3298
|
4
|
+
reyserver/rclient.py,sha256=OjY_OEH_USrJK8TuCevXG6jNPVbM0lN-xMBozB7kSM8,2195
|
5
|
+
reyserver/rfile.py,sha256=pA1LdHjBDJwj7S2k7cemwsHO19Oo0bHtkX_r79RBlJA,8770
|
6
|
+
reyserver/rserver.py,sha256=lsOr_fhiAukQhLHfqH1OfZByuUEzjFoJtqLI7BoNxvI,3401
|
7
|
+
reyserver-1.1.46.dist-info/METADATA,sha256=3E8xJWPt5S6j3piVBeuAbFSXzqbTHq6hhrEp5EJ0YLg,1658
|
8
|
+
reyserver-1.1.46.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
9
|
+
reyserver-1.1.46.dist-info/licenses/LICENSE,sha256=UYLPqp7BvPiH8yEZduJqmmyEl6hlM3lKrFIefiD4rvk,1059
|
10
|
+
reyserver-1.1.46.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=PXqgNMVZfy8JEziX_GlMNAel_usmwN7s9ssJEJXdTRM,2338
|
4
|
-
reyserver/rclient.py,sha256=rrqTPT_Y8zY9KIoPALKcIFUADg8HTa2YH9782tfGp3c,642
|
5
|
-
reyserver/rfile.py,sha256=JhHQSvJRa4Hq3qsHCE_UwoS0QPHtk7mw60KcC0YFe8E,7464
|
6
|
-
reyserver/rserver.py,sha256=sbuCkCF1-B2ZcYctapU3PrcqyLaETcumgipA_vYhGgQ,3400
|
7
|
-
reyserver-1.1.44.dist-info/METADATA,sha256=GJ4qp0xSxiku6Yp9FZrkaY-qQxCfVHuG_JM0RKfZJIc,1658
|
8
|
-
reyserver-1.1.44.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
9
|
-
reyserver-1.1.44.dist-info/licenses/LICENSE,sha256=UYLPqp7BvPiH8yEZduJqmmyEl6hlM3lKrFIefiD4rvk,1059
|
10
|
-
reyserver-1.1.44.dist-info/RECORD,,
|
File without changes
|
File without changes
|