fastapi-rtk 1.0.7__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.
@@ -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(self.get_path(filename), "rb") as f:
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(self.get_path(filename), "rb") as f:
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(path, "rb").close() # Check if file exists
82
- s3 = self.boto3.client(
83
- "s3",
84
- aws_access_key_id=self.access_key,
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", lazy="selectin"
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(