fastapi-rtk 1.0.8__py3-none-any.whl → 1.0.9__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.
- fastapi_rtk/__init__.py +0 -1
- fastapi_rtk/_version.py +1 -1
- fastapi_rtk/auth/auth.py +0 -9
- fastapi_rtk/bases/file_manager.py +12 -0
- fastapi_rtk/dependencies.py +110 -64
- fastapi_rtk/fastapi_react_toolkit.py +101 -157
- fastapi_rtk/file_managers/s3_file_manager.py +63 -32
- fastapi_rtk/security/sqla/apis.py +18 -3
- fastapi_rtk/security/sqla/models.py +8 -27
- fastapi_rtk/security/sqla/security_manager.py +367 -10
- fastapi_rtk/utils/hooks.py +7 -4
- {fastapi_rtk-1.0.8.dist-info → fastapi_rtk-1.0.9.dist-info}/METADATA +1 -1
- {fastapi_rtk-1.0.8.dist-info → fastapi_rtk-1.0.9.dist-info}/RECORD +16 -16
- {fastapi_rtk-1.0.8.dist-info → fastapi_rtk-1.0.9.dist-info}/WHEEL +0 -0
- {fastapi_rtk-1.0.8.dist-info → fastapi_rtk-1.0.9.dist-info}/entry_points.txt +0 -0
- {fastapi_rtk-1.0.8.dist-info → fastapi_rtk-1.0.9.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
1
3
|
from ..bases.file_manager import AbstractFileManager
|
|
2
4
|
from ..const import logger
|
|
3
5
|
from ..utils import smart_run
|
|
@@ -12,20 +14,58 @@ class S3FileManager(AbstractFileManager):
|
|
|
12
14
|
|
|
13
15
|
def __init__(
|
|
14
16
|
self,
|
|
15
|
-
base_path=None,
|
|
16
|
-
allowed_extensions=None,
|
|
17
|
-
namegen=None,
|
|
18
|
-
permission=None,
|
|
19
|
-
bucket_name=None,
|
|
20
|
-
bucket_subfolder=None,
|
|
21
|
-
access_key=None,
|
|
22
|
-
secret_key=None,
|
|
17
|
+
base_path: str | None = None,
|
|
18
|
+
allowed_extensions: list[str] | None = None,
|
|
19
|
+
namegen: typing.Callable[[str], str] | None = None,
|
|
20
|
+
permission: int | None = None,
|
|
21
|
+
bucket_name: str | None = None,
|
|
22
|
+
bucket_subfolder: str | None = None,
|
|
23
|
+
access_key: str | None = None,
|
|
24
|
+
secret_key: str | None = None,
|
|
25
|
+
open_params: dict[str, typing.Any] | None = None,
|
|
26
|
+
boto3_client: typing.Any | None = None,
|
|
23
27
|
):
|
|
28
|
+
"""
|
|
29
|
+
Initializes the S3FileManager.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
base_path (str | None, optional): URL path to the S3 bucket. Defaults to None.
|
|
33
|
+
allowed_extensions (list[str] | None, optional): Allowed file extensions. Defaults to None.
|
|
34
|
+
namegen (typing.Callable[[str], str] | None, optional): Callable for generating file names. Defaults to None.
|
|
35
|
+
permission (int | None, optional): File permission settings. Defaults to None.
|
|
36
|
+
bucket_name (str | None, optional): Name of the S3 bucket. Defaults to None.
|
|
37
|
+
bucket_subfolder (str | None, optional): Subfolder within the S3 bucket. Defaults to None.
|
|
38
|
+
access_key (str | None, optional): AWS access key. Needed for default boto3 client in order to delete files. Defaults to None.
|
|
39
|
+
secret_key (str | None, optional): AWS secret key. Needed for default boto3 client in order to delete files. Defaults to None.
|
|
40
|
+
open_params (dict[str, typing.Any] | None, optional): Parameters for opening files. Defaults to None.
|
|
41
|
+
boto3_client (typing.Any | None, optional): Boto3 client instance. If None, a new client will be created. Defaults to None.
|
|
42
|
+
Raises:
|
|
43
|
+
ImportError: If required libraries are not installed.
|
|
44
|
+
"""
|
|
24
45
|
super().__init__(base_path, allowed_extensions, namegen, permission)
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
import boto3
|
|
49
|
+
import smart_open
|
|
50
|
+
|
|
51
|
+
self.smart_open = smart_open
|
|
52
|
+
self.boto3 = boto3
|
|
53
|
+
except ImportError:
|
|
54
|
+
raise ImportError(
|
|
55
|
+
"smart_open is required for S3FileManager. "
|
|
56
|
+
"Please install it with 'pip install smart_open[s3]'."
|
|
57
|
+
)
|
|
58
|
+
|
|
25
59
|
self.bucket_name = bucket_name
|
|
26
60
|
self.bucket_subfolder = bucket_subfolder
|
|
27
61
|
self.access_key = access_key
|
|
28
62
|
self.secret_key = secret_key
|
|
63
|
+
self.open_params = open_params or {}
|
|
64
|
+
self.boto3_client = boto3_client or self.boto3.client(
|
|
65
|
+
"s3",
|
|
66
|
+
aws_access_key_id=self.access_key,
|
|
67
|
+
aws_secret_access_key=self.secret_key,
|
|
68
|
+
)
|
|
29
69
|
|
|
30
70
|
if not self.bucket_name:
|
|
31
71
|
logger.warning(
|
|
@@ -39,52 +79,41 @@ class S3FileManager(AbstractFileManager):
|
|
|
39
79
|
"Files may not be able to be deleted"
|
|
40
80
|
)
|
|
41
81
|
|
|
42
|
-
try:
|
|
43
|
-
import boto3
|
|
44
|
-
import smart_open
|
|
45
|
-
|
|
46
|
-
self.smart_open = smart_open
|
|
47
|
-
self.boto3 = boto3
|
|
48
|
-
except ImportError:
|
|
49
|
-
raise ImportError(
|
|
50
|
-
"smart_open is required for S3FileManager. "
|
|
51
|
-
"Please install it with 'pip install smart_open[s3]'."
|
|
52
|
-
)
|
|
53
|
-
|
|
54
82
|
def get_path(self, filename):
|
|
55
83
|
return self.base_path + "/" + filename
|
|
56
84
|
|
|
57
85
|
def get_file(self, filename):
|
|
58
|
-
with self.smart_open.open(
|
|
86
|
+
with self.smart_open.open(
|
|
87
|
+
self.get_path(filename), "rb", **self.open_params
|
|
88
|
+
) as f:
|
|
59
89
|
return f.read()
|
|
60
90
|
|
|
61
91
|
async def stream_file(self, filename):
|
|
62
|
-
with self.smart_open.open(
|
|
92
|
+
with self.smart_open.open(
|
|
93
|
+
self.get_path(filename), "rb", **self.open_params
|
|
94
|
+
) as f:
|
|
63
95
|
while chunk := await smart_run(f.read, 8192):
|
|
64
96
|
yield chunk
|
|
65
97
|
|
|
66
98
|
def save_file(self, file_data, filename):
|
|
67
99
|
path = self.get_path(filename)
|
|
68
|
-
with self.smart_open.open(path, "wb") as f:
|
|
100
|
+
with self.smart_open.open(path, "wb", **self.open_params) as f:
|
|
69
101
|
f.write(file_data.file.read())
|
|
70
102
|
return path
|
|
71
103
|
|
|
72
104
|
def save_content_to_file(self, content, filename):
|
|
73
105
|
path = self.get_path(filename)
|
|
74
|
-
with self.smart_open.open(path, "wb") as f:
|
|
106
|
+
with self.smart_open.open(path, "wb", **self.open_params) as f:
|
|
75
107
|
f.write(content)
|
|
76
108
|
return path
|
|
77
109
|
|
|
78
110
|
def delete_file(self, filename):
|
|
79
111
|
path = self.get_path(filename)
|
|
80
112
|
try:
|
|
81
|
-
self.smart_open.open(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
aws_secret_access_key=self.secret_key,
|
|
86
|
-
)
|
|
87
|
-
s3.delete_object(
|
|
113
|
+
self.smart_open.open(
|
|
114
|
+
path, "rb", **self.open_params
|
|
115
|
+
).close() # Check if file exists
|
|
116
|
+
self.boto3_client.delete_object(
|
|
88
117
|
Bucket=self.bucket_name,
|
|
89
118
|
Key=f"{self.bucket_subfolder}/{filename}"
|
|
90
119
|
if self.bucket_subfolder
|
|
@@ -96,7 +125,7 @@ class S3FileManager(AbstractFileManager):
|
|
|
96
125
|
def file_exists(self, filename):
|
|
97
126
|
path = self.get_path(filename)
|
|
98
127
|
try:
|
|
99
|
-
with self.smart_open.open(path, "rb"):
|
|
128
|
+
with self.smart_open.open(path, "rb", **self.open_params):
|
|
100
129
|
return True
|
|
101
130
|
except FileNotFoundError:
|
|
102
131
|
return False
|
|
@@ -110,6 +139,8 @@ class S3FileManager(AbstractFileManager):
|
|
|
110
139
|
else subfolder,
|
|
111
140
|
access_key=self.access_key,
|
|
112
141
|
secret_key=self.secret_key,
|
|
142
|
+
open_params=self.open_params,
|
|
143
|
+
boto3_client=self.boto3_client,
|
|
113
144
|
*args,
|
|
114
145
|
**kwargs,
|
|
115
146
|
)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
|
+
import sqlalchemy
|
|
3
4
|
from fastapi import Depends, HTTPException, Request, status
|
|
4
5
|
from pydantic import BaseModel, EmailStr, Field
|
|
5
6
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
@@ -8,7 +9,7 @@ from sqlalchemy.orm import Session
|
|
|
8
9
|
from ...api import BaseApi, ModelRestApi
|
|
9
10
|
from ...backends.sqla.interface import SQLAInterface
|
|
10
11
|
from ...const import ErrorCode
|
|
11
|
-
from ...db import UserDatabase, get_user_db
|
|
12
|
+
from ...db import UserDatabase, db, get_user_db
|
|
12
13
|
from ...decorators import expose, login_required
|
|
13
14
|
from ...globals import g
|
|
14
15
|
from ...lang import translate
|
|
@@ -21,7 +22,7 @@ from ...schemas import (
|
|
|
21
22
|
generate_user_update_schema,
|
|
22
23
|
)
|
|
23
24
|
from ...setting import Setting
|
|
24
|
-
from ...utils import SelfType, lazy, merge_schema
|
|
25
|
+
from ...utils import SelfType, lazy, merge_schema, smart_run
|
|
25
26
|
from .models import Api, Permission, PermissionApi, Role, User
|
|
26
27
|
|
|
27
28
|
__all__ = [
|
|
@@ -275,12 +276,26 @@ class AuthApi(BaseApi):
|
|
|
275
276
|
},
|
|
276
277
|
)(self.update_user)
|
|
277
278
|
|
|
278
|
-
def get_user(self):
|
|
279
|
+
async def get_user(self):
|
|
279
280
|
if not g.user:
|
|
280
281
|
raise HTTPException(
|
|
281
282
|
status.HTTP_401_UNAUTHORIZED,
|
|
282
283
|
ErrorCode.GET_USER_MISSING_TOKEN_OR_INACTIVE_USER,
|
|
283
284
|
)
|
|
285
|
+
|
|
286
|
+
g.user.permissions = []
|
|
287
|
+
if g.user.roles:
|
|
288
|
+
# Retrieve list of api names that user has access to
|
|
289
|
+
query = (
|
|
290
|
+
sqlalchemy.select(Api.name)
|
|
291
|
+
.join(PermissionApi)
|
|
292
|
+
.join(PermissionApi.roles)
|
|
293
|
+
.where(Role.id.in_([role.id for role in g.user.roles]))
|
|
294
|
+
.distinct()
|
|
295
|
+
)
|
|
296
|
+
result = await smart_run(db.current_session.scalars, query)
|
|
297
|
+
g.user.permissions = list(result)
|
|
298
|
+
|
|
284
299
|
return g.user
|
|
285
300
|
|
|
286
301
|
async def update_user(
|
|
@@ -76,22 +76,15 @@ class PermissionApi(Model):
|
|
|
76
76
|
api_id: Mapped[int] = mapped_column(
|
|
77
77
|
Integer, ForeignKey(f"{API_TABLE}.id"), name=view_menu_id, nullable=False
|
|
78
78
|
)
|
|
79
|
-
api: Mapped["Api"] = relationship(
|
|
80
|
-
"Api", back_populates="permissions", lazy="selectin"
|
|
81
|
-
)
|
|
79
|
+
api: Mapped["Api"] = relationship("Api", back_populates="permissions")
|
|
82
80
|
|
|
83
81
|
permission_id: Mapped[int] = mapped_column(
|
|
84
82
|
Integer, ForeignKey(f"{PERMISSION_TABLE}.id"), nullable=False
|
|
85
83
|
)
|
|
86
|
-
permission: Mapped["Permission"] = relationship(
|
|
87
|
-
"Permission", back_populates="apis", lazy="selectin"
|
|
88
|
-
)
|
|
84
|
+
permission: Mapped["Permission"] = relationship("Permission", back_populates="apis")
|
|
89
85
|
|
|
90
86
|
roles: Mapped[list["Role"]] = relationship(
|
|
91
|
-
"Role",
|
|
92
|
-
secondary=assoc_permission_api_role,
|
|
93
|
-
back_populates="permissions",
|
|
94
|
-
lazy="selectin",
|
|
87
|
+
"Role", secondary=assoc_permission_api_role, back_populates="permissions"
|
|
95
88
|
)
|
|
96
89
|
|
|
97
90
|
def __repr__(self) -> str:
|
|
@@ -104,10 +97,7 @@ class Api(Model):
|
|
|
104
97
|
name: Mapped[str] = mapped_column(String, unique=True, nullable=False)
|
|
105
98
|
|
|
106
99
|
permissions: Mapped[list[PermissionApi]] = relationship(
|
|
107
|
-
PermissionApi,
|
|
108
|
-
back_populates="api",
|
|
109
|
-
lazy="selectin",
|
|
110
|
-
cascade="all, delete-orphan",
|
|
100
|
+
PermissionApi, back_populates="api", cascade="all, delete-orphan"
|
|
111
101
|
)
|
|
112
102
|
|
|
113
103
|
def __eq__(self, other):
|
|
@@ -128,10 +118,7 @@ class Permission(Model):
|
|
|
128
118
|
name: Mapped[str] = mapped_column(String, unique=True, nullable=False)
|
|
129
119
|
|
|
130
120
|
apis: Mapped[list[PermissionApi]] = relationship(
|
|
131
|
-
PermissionApi,
|
|
132
|
-
back_populates="permission",
|
|
133
|
-
lazy="selectin",
|
|
134
|
-
cascade="all, delete-orphan",
|
|
121
|
+
PermissionApi, back_populates="permission", cascade="all, delete-orphan"
|
|
135
122
|
)
|
|
136
123
|
|
|
137
124
|
def __repr__(self) -> str:
|
|
@@ -144,14 +131,11 @@ class Role(Model):
|
|
|
144
131
|
name: Mapped[str] = mapped_column(String, unique=True, nullable=False)
|
|
145
132
|
|
|
146
133
|
users: Mapped[list["User"]] = relationship(
|
|
147
|
-
"User", secondary=assoc_user_role, back_populates="roles"
|
|
134
|
+
"User", secondary=assoc_user_role, back_populates="roles"
|
|
148
135
|
)
|
|
149
136
|
|
|
150
137
|
permissions: Mapped[list[PermissionApi]] = relationship(
|
|
151
|
-
PermissionApi,
|
|
152
|
-
secondary=assoc_permission_api_role,
|
|
153
|
-
back_populates="roles",
|
|
154
|
-
lazy="selectin",
|
|
138
|
+
PermissionApi, secondary=assoc_permission_api_role, back_populates="roles"
|
|
155
139
|
)
|
|
156
140
|
|
|
157
141
|
def __repr__(self) -> str:
|
|
@@ -203,10 +187,7 @@ class User(Model):
|
|
|
203
187
|
return Column(Integer, ForeignKey("ab_user.id"), default=self.get_user_id)
|
|
204
188
|
|
|
205
189
|
oauth_accounts: Mapped[list[OAuthAccount]] = relationship(
|
|
206
|
-
"OAuthAccount",
|
|
207
|
-
back_populates="user",
|
|
208
|
-
lazy="selectin",
|
|
209
|
-
cascade="all, delete-orphan",
|
|
190
|
+
"OAuthAccount", back_populates="user", cascade="all, delete-orphan"
|
|
210
191
|
)
|
|
211
192
|
|
|
212
193
|
roles: Mapped[list[Role]] = relationship(
|