fastapi-rtk 1.0.20__py3-none-any.whl → 1.0.21__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/_version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "1.0.20"
1
+ __version__ = "1.0.21"
@@ -1883,20 +1883,21 @@ class ModelRestApi(BaseApi):
1883
1883
  if not isinstance(filenames, list):
1884
1884
  filenames = [filenames]
1885
1885
  for filename in filenames:
1886
- old_content = await smart_run(fm.get_file, filename)
1887
1886
  before_commit_runner.add_task(
1888
1887
  lambda fm=fm, filename=filename: smart_run(
1889
1888
  fm.delete_file, filename
1890
1889
  )
1891
1890
  )
1892
- after_commit_runner.add_task(
1893
- lambda fm=fm,
1894
- content=old_content,
1895
- filename=filename: smart_run(
1896
- fm.save_content_to_file, content, filename
1897
- ),
1898
- tags=["file"],
1899
- )
1891
+ if fm.file_exists(filename):
1892
+ old_content = await smart_run(fm.get_file, filename)
1893
+ after_commit_runner.add_task(
1894
+ lambda fm=fm,
1895
+ content=old_content,
1896
+ filename=filename: smart_run(
1897
+ fm.save_content_to_file, content, filename
1898
+ ),
1899
+ tags=["file"],
1900
+ )
1900
1901
  await smart_run(self.datamodel.delete, session, item)
1901
1902
  after_commit_runner.remove_tasks_by_tag(
1902
1903
  "file"
@@ -2410,21 +2411,26 @@ class ModelRestApi(BaseApi):
2410
2411
  # Delete only the files or images that are not in the new old_filenames
2411
2412
  for filename in actual_old_filenames:
2412
2413
  if filename not in old_filenames:
2413
- old_content = await smart_run(fm.get_file, filename)
2414
2414
  before_commit_runner.add_task(
2415
2415
  lambda fm=fm, old_filename=filename: smart_run(
2416
2416
  fm.delete_file, old_filename
2417
2417
  ),
2418
2418
  tags=["file"],
2419
2419
  )
2420
- after_commit_runner.add_task(
2421
- lambda fm=fm,
2422
- content=old_content,
2423
- filename=filename: smart_run(
2424
- fm.save_content_to_file, content, filename
2425
- ),
2426
- tags=["file"],
2427
- )
2420
+ if fm.file_exists(filename):
2421
+ old_content = await smart_run(
2422
+ fm.get_file, filename
2423
+ )
2424
+ after_commit_runner.add_task(
2425
+ lambda fm=fm,
2426
+ content=old_content,
2427
+ filename=filename: smart_run(
2428
+ fm.save_content_to_file,
2429
+ content,
2430
+ filename,
2431
+ ),
2432
+ tags=["file"],
2433
+ )
2428
2434
 
2429
2435
  new_filenames = []
2430
2436
  # Loop through value instead of only file values so the order is maintained
@@ -2440,27 +2446,28 @@ class ModelRestApi(BaseApi):
2440
2446
  # Delete existing file or image if it is being updated
2441
2447
  if item and hasattr(item, key) and getattr(item, key):
2442
2448
  filename = getattr(item, key)
2443
- old_content = await smart_run(fm.get_file, filename)
2444
2449
  before_commit_runner = AsyncTaskRunner.get_runner(
2445
2450
  "before_commit"
2446
2451
  )
2447
- after_commit_runner = AsyncTaskRunner.get_runner(
2448
- "after_commit"
2449
- )
2450
2452
  before_commit_runner.add_task(
2451
2453
  lambda fm=fm, old_filename=filename: smart_run(
2452
2454
  fm.delete_file, old_filename
2453
2455
  ),
2454
2456
  tags=["file"],
2455
2457
  )
2456
- after_commit_runner.add_task(
2457
- lambda fm=fm,
2458
- content=old_content,
2459
- filename=filename: smart_run(
2460
- fm.save_content_to_file, content, filename
2461
- ),
2462
- tags=["file"],
2463
- )
2458
+ if fm.file_exists(filename):
2459
+ old_content = await smart_run(fm.get_file, filename)
2460
+ after_commit_runner = AsyncTaskRunner.get_runner(
2461
+ "after_commit"
2462
+ )
2463
+ after_commit_runner.add_task(
2464
+ lambda fm=fm,
2465
+ content=old_content,
2466
+ filename=filename: smart_run(
2467
+ fm.save_content_to_file, content, filename
2468
+ ),
2469
+ tags=["file"],
2470
+ )
2464
2471
 
2465
2472
  # Only process if the value exists and is not None
2466
2473
  if value:
@@ -268,6 +268,7 @@ class AbstractFileManager(abc.ABC):
268
268
  return self.__class__(
269
269
  base_path=f"{self.base_path}/{subfolder}",
270
270
  allowed_extensions=self.allowed_extensions,
271
+ max_file_size=self.max_file_size,
271
272
  namegen=self.namegen,
272
273
  permission=self.permission,
273
274
  *args,
@@ -2,7 +2,7 @@ import typing
2
2
 
3
3
  from ..bases.file_manager import AbstractFileManager
4
4
  from ..setting import Setting
5
- from ..utils import lazy, smart_run, use_default_when_none
5
+ from ..utils import T, lazy, smart_run, use_default_when_none
6
6
 
7
7
  __all__ = ["S3FileManager"]
8
8
 
@@ -14,6 +14,7 @@ class S3FileManager(AbstractFileManager):
14
14
 
15
15
  allowed_extensions = lazy(lambda: Setting.FILE_ALLOWED_EXTENSIONS)
16
16
  max_file_size = lazy(lambda: Setting.FILE_MAX_SIZE)
17
+ ERROR_CODE_FILE_NOT_FOUND = "NoSuchKey"
17
18
 
18
19
  def __init__(
19
20
  self,
@@ -57,10 +58,14 @@ class S3FileManager(AbstractFileManager):
57
58
 
58
59
  try:
59
60
  import boto3
61
+ import botocore
62
+ import botocore.client
60
63
  import smart_open
61
64
 
62
65
  self.smart_open = smart_open
63
66
  self.boto3 = boto3
67
+ self.botocore = botocore
68
+ self.botocore.client = botocore.client
64
69
  except ImportError:
65
70
  raise ImportError(
66
71
  "smart_open is required for S3FileManager. "
@@ -125,27 +130,22 @@ class S3FileManager(AbstractFileManager):
125
130
  return path
126
131
 
127
132
  def delete_file(self, filename):
128
- path = self.get_path(filename)
129
- try:
130
- self.smart_open.open(
131
- path, "rb", **self.open_params
132
- ).close() # Check if file exists
133
+ if self.file_exists(filename):
133
134
  self.boto3_client.delete_object(
134
135
  Bucket=self.bucket_name,
135
136
  Key=f"{self.bucket_subfolder}/{filename}"
136
137
  if self.bucket_subfolder
137
138
  else filename,
138
139
  )
139
- except FileNotFoundError:
140
- pass
141
140
 
142
141
  def file_exists(self, filename):
143
142
  path = self.get_path(filename)
144
143
  try:
145
- with self.smart_open.open(path, "rb", **self.open_params):
146
- return True
147
- except FileNotFoundError:
148
- return False
144
+ with self.smart_open.open(path, "rb", **self.open_params) as f:
145
+ f.read(1) # Try to read a byte to confirm existence
146
+ return True
147
+ except IOError as e:
148
+ return self._handle_io_error(e, value_to_return=False)
149
149
 
150
150
  def get_instance_with_subfolder(self, subfolder, *args, **kwargs):
151
151
  return super().get_instance_with_subfolder(
@@ -161,3 +161,13 @@ class S3FileManager(AbstractFileManager):
161
161
  *args,
162
162
  **kwargs,
163
163
  )
164
+
165
+ def _handle_io_error(self, e: IOError, *, value_to_return: T = None):
166
+ if hasattr(e, "backend_error") and isinstance(
167
+ e.backend_error, self.botocore.client.ClientError
168
+ ):
169
+ error = e.backend_error.response.get("Error", {})
170
+ error_code = error.get("Code")
171
+ if error_code == self.ERROR_CODE_FILE_NOT_FOUND:
172
+ return value_to_return
173
+ raise e
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-rtk
3
- Version: 1.0.20
3
+ Version: 1.0.21
4
4
  Summary: A package that provides a set of tools to build a FastAPI application with a Class-Based CRUD API.
5
5
  Project-URL: Homepage, https://codeberg.org/datatactics/fastapi-rtk
6
6
  Project-URL: Issues, https://codeberg.org/datatactics/fastapi-rtk/issues
@@ -1,5 +1,5 @@
1
1
  fastapi_rtk/__init__.py,sha256=TGDmH62gymFCC6yFdAsiVHcdOvy4_tdDCzAK4WzEHH8,6264
2
- fastapi_rtk/_version.py,sha256=D3FIR1OP9p54JyJSpU5wDfDWvaAjk2k-IH5FzjX_kCA,23
2
+ fastapi_rtk/_version.py,sha256=lJ4ivbKbnYXFf2Ku8SMHBz5WRCggnAB-EiaLLFW9B-8,23
3
3
  fastapi_rtk/apis.py,sha256=6X_Lhl98m7lKrDRybg2Oe24pLFLJ29eCOQSwCAvpKhY,172
4
4
  fastapi_rtk/config.py,sha256=9PZF9E5i1gxmnsZEprZZKxVHSk0dFEklJSplX9NEqdo,14036
5
5
  fastapi_rtk/const.py,sha256=sEj_cYeerj9pVwbCu0k5Sy1EYpdr1EHzUjqqbnporgc,4905
@@ -21,7 +21,7 @@ fastapi_rtk/types.py,sha256=-LPnTIbHvqJW81__gab3EWrhjNmznHhptz0BtXkEAHQ,3612
21
21
  fastapi_rtk/version.py,sha256=D2cmQf2LNeHOiEfcNzVOOfcAmuLvPEmGEtZv5G54D0c,195
22
22
  fastapi_rtk/api/__init__.py,sha256=MwFR7HHppnhbjZGg3sOdQ6nqy9uxnHHXvicpswNFMNA,245
23
23
  fastapi_rtk/api/base_api.py,sha256=42I9v3b25lqxNAMDGEtajA5-btIDSyUWF0xMDgGkA8c,8078
24
- fastapi_rtk/api/model_rest_api.py,sha256=B29CA1pOoCyoyKbknPmnIfgbYBLAWKD8pRdBlPh0RA4,111332
24
+ fastapi_rtk/api/model_rest_api.py,sha256=YNwIBDBm7jWNqN9KExZ5bLIxdLSNDuRsDpJOjLf6CkU,111821
25
25
  fastapi_rtk/auth/__init__.py,sha256=iX7O41NivBYDfdomEaqm4lUx9KD17wI4g3EFLF6kUTw,336
26
26
  fastapi_rtk/auth/auth.py,sha256=F2qZoR7go_7FnvVJrDxUCd6vtRz5XW8yyOv143MWPts,20664
27
27
  fastapi_rtk/auth/hashers/__init__.py,sha256=uBThFj2VPPSMSioxYTktNiM4-mVgtDAjTpKA3ZzWxxs,110
@@ -60,7 +60,7 @@ fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py,sha256=L1WgO7UzWuAgs
60
60
  fastapi_rtk/backends/sqla/extensions/geoalchemy2/geometry_converter.py,sha256=sckNoxPE8ApKCLgBZzE_2dokXrM6mIXvMguHZvyJzIM,3891
61
61
  fastapi_rtk/bases/__init__.py,sha256=Te9rcmi1AGG72iZCzGyvc5qEIVyIexN7ViFdb59a2RA,494
62
62
  fastapi_rtk/bases/db.py,sha256=D27BhF89J0OaLHjALDCa85eNf35lBaTz6VV7EDa4wuM,18711
63
- fastapi_rtk/bases/file_manager.py,sha256=A5Tt_BYQQc3cNYzWxJ5tCKcQe3HOxhXcrBoyWx6wtnc,11557
63
+ fastapi_rtk/bases/file_manager.py,sha256=sT49sYA-KFVZS4I4_uf4838QBmKBUPBlklFfHhz2M7Y,11603
64
64
  fastapi_rtk/bases/filter.py,sha256=XmWTcLaIcBj9pKF1PMAKdwSnZNpdT8Df3uLeUIOGUDE,1840
65
65
  fastapi_rtk/bases/interface.py,sha256=Cq9Duxa3w-tw342P424h88fc0_X1DoxCdTa3rAN-6jM,45380
66
66
  fastapi_rtk/bases/model.py,sha256=nUZf0AVs0Mzqh2u_ALiRNYN1bfOU9PzYLvEFHDQ57Y0,1692
@@ -87,7 +87,7 @@ fastapi_rtk/cli/commands/db/templates/fastapi-multidb/script.py.mako,sha256=o02U
87
87
  fastapi_rtk/file_managers/__init__.py,sha256=RZzSSUDq_cIv4MeVePRBGR0mbEiJua7WKccfE6IAawk,198
88
88
  fastapi_rtk/file_managers/file_manager.py,sha256=eEoaoh1pw0negbNNo7z8adijwCR_Y6ZWlbIeWK-9ZB0,2668
89
89
  fastapi_rtk/file_managers/image_manager.py,sha256=4Tq1m0cdEYXGPJdjXJoEJWERs1vL6Ejfwyd_MBHp71U,628
90
- fastapi_rtk/file_managers/s3_file_manager.py,sha256=Rb5rEet2R72VFUHMPITJugVLhsTjxVh4oqrL7TjKZVo,6424
90
+ fastapi_rtk/file_managers/s3_file_manager.py,sha256=8lxmk889N0CQwi7HryfFp4YVPuwHoGEIMG6gHHQ9YBo,6962
91
91
  fastapi_rtk/file_managers/s3_image_manager.py,sha256=FBwdGu9FWDPjrUQVTvTyZNnyZvS5-hd-fkxQLOsLa80,533
92
92
  fastapi_rtk/lang/__init__.py,sha256=zJejpbIXx1O-nSuWwrCYgr5f5EtwjYW6kh8jRy_Yo0U,68
93
93
  fastapi_rtk/lang/babel.cfg,sha256=ahkzg8wUKyl9G9UqkgAyWEtRlCnaWkjStM5c4FhKuUE,18
@@ -126,8 +126,8 @@ fastapi_rtk/utils/timezone.py,sha256=62S0pPWuDFFXxV1YTFCsc4uKiSP_Ba36Fv7S3gYjfhs
126
126
  fastapi_rtk/utils/update_signature.py,sha256=PIzZgNpGEwvDNgQ3G51Zi-QhVV3mbxvUvSwDwf_-yYs,2209
127
127
  fastapi_rtk/utils/use_default_when_none.py,sha256=H2HqhKy_8eYk3i1xijEjuaKak0oWkMIkrdz6T7DK9QU,469
128
128
  fastapi_rtk/utils/werkzeug.py,sha256=1Gv-oyqSmhVGrmNbB9fDqpqJzPpANOzWf4zMMrhW9UA,3245
129
- fastapi_rtk-1.0.20.dist-info/METADATA,sha256=3HSGbdKbJ8IG24nA3D8HxptAVRj9NtYieA-3tF5GSZY,1302
130
- fastapi_rtk-1.0.20.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
131
- fastapi_rtk-1.0.20.dist-info/entry_points.txt,sha256=UuTkxSVIokSlVN28TMhoxzRRUaPxlVRSH3Gsx6yip60,53
132
- fastapi_rtk-1.0.20.dist-info/licenses/LICENSE,sha256=NDrWi4Qwcxal3u1r2lBWGA6TVh3OeW7yMan098mQz98,1073
133
- fastapi_rtk-1.0.20.dist-info/RECORD,,
129
+ fastapi_rtk-1.0.21.dist-info/METADATA,sha256=U6I4Prujp70tf_W94pKGxXcxZaGzUwB8xld2Y700YbI,1302
130
+ fastapi_rtk-1.0.21.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
131
+ fastapi_rtk-1.0.21.dist-info/entry_points.txt,sha256=UuTkxSVIokSlVN28TMhoxzRRUaPxlVRSH3Gsx6yip60,53
132
+ fastapi_rtk-1.0.21.dist-info/licenses/LICENSE,sha256=NDrWi4Qwcxal3u1r2lBWGA6TVh3OeW7yMan098mQz98,1073
133
+ fastapi_rtk-1.0.21.dist-info/RECORD,,