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 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 reydb import DatabaseAsync
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 create_depend_sess(database: str):
129
+ def create_depend_db(database: str, mode: Literal['sess', 'conn']):
93
130
  """
94
- Create dependencie function of asynchronous database session.
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 depend_sess():
146
+ async def depend():
107
147
  """
108
- Dependencie function of asynchronous database session.
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
- async with engine.orm.session() as sess:
116
- yield sess
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 depend_sess
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(self): ...
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 typing import Annotated
13
- from fastapi import APIRouter, Form, File, UploadFile, Depends
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, create_depend_sess
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
- 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
- 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', prefix='/file') -> None:
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=prefix)
134
+ 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.44
3
+ Version: 1.1.46
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>
@@ -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,,