rb-commons 0.1.21__py3-none-any.whl → 0.1.23__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.
@@ -20,6 +20,10 @@ class CommonConfigs(BaseSettings):
20
20
  POSTGRES_DB: Optional[str] = None
21
21
  DB_ALEMBIC_URL: Optional[str] = None
22
22
 
23
+ DIGITALOCEAN_STORAGE_BUCKET_NAME: Optional[str] = None
24
+ DIGITALOCEAN_S3_ENDPOINT_URL: Optional[str] = None
25
+
26
+
23
27
  @property
24
28
  def database_url(self) -> Optional[str]:
25
29
  """Construct the database URL if all required fields are present."""
@@ -1,9 +1,9 @@
1
1
  import uuid
2
2
  from typing import TypeVar, Type, Generic, Optional, List, Dict, Literal, Union
3
- from sqlalchemy import select, delete, update
3
+ from sqlalchemy import select, delete, update, and_
4
4
  from sqlalchemy.exc import IntegrityError, SQLAlchemyError, NoResultFound
5
5
  from sqlalchemy.ext.asyncio import AsyncSession
6
- from sqlalchemy.orm import declarative_base
6
+ from sqlalchemy.orm import declarative_base, InstrumentedAttribute
7
7
 
8
8
  from rb_commons.http.exceptions import NotFoundException
9
9
  from rb_commons.orm.exceptions import DatabaseException, InternalException
@@ -16,7 +16,8 @@ class BaseManager(Generic[ModelType]):
16
16
  def __init__(self, session: AsyncSession) -> None:
17
17
  self.session = session
18
18
  self.data = None
19
- self.filters = {}
19
+ self.filters = []
20
+ self._filtered = False
20
21
 
21
22
  async def get(self, pk: Union[str, int, uuid.UUID]) -> Optional[ModelType]:
22
23
  """
@@ -37,26 +38,82 @@ class BaseManager(Generic[ModelType]):
37
38
 
38
39
  def filter(self, **kwargs) -> 'BaseManager[ModelType]':
39
40
  """
40
- Filter objects based on conditions
41
+ Apply filtering conditions dynamically.
42
+
43
+ Supports:
44
+ - `field__eq`: Equal (`=`)
45
+ - `field__ne`: Not Equal (`!=`)
46
+ - `field__gt`: Greater Than (`>`)
47
+ - `field__lt`: Less Than (`<`)
48
+ - `field__gte`: Greater Than or Equal (`>=`)
49
+ - `field__lte`: Less Than or Equal (`<=`)
50
+ - `field__in`: IN Query
51
+ - `field__contains`: LIKE Query
41
52
  """
42
- self.filters.update(kwargs)
53
+ self._filtered = True
54
+ self.filters = []
55
+
56
+ for key, value in kwargs.items():
57
+ if '__' in key:
58
+ field_name, operator = key.split('__', 1)
59
+ else:
60
+ field_name, operator = key, 'eq'
61
+
62
+ column = getattr(self.model, field_name, None)
63
+ if column is None or not isinstance(column, InstrumentedAttribute):
64
+ raise ValueError(f"Invalid filter field: {field_name}")
65
+
66
+ if operator == "eq":
67
+ self.filters.append(column == value)
68
+ elif operator == "ne":
69
+ self.filters.append(column != value)
70
+ elif operator == "gt":
71
+ self.filters.append(column > value)
72
+ elif operator == "lt":
73
+ self.filters.append(column < value)
74
+ elif operator == "gte":
75
+ self.filters.append(column >= value)
76
+ elif operator == "lte":
77
+ self.filters.append(column <= value)
78
+ elif operator == "in" and isinstance(value, list):
79
+ if not isinstance(value, list):
80
+ raise ValueError(f"`{field_name}__in` requires a list, got {type(value)}")
81
+
82
+ self.filters.append(column.in_(value))
83
+ elif operator == "contains":
84
+ self.filters.append(column.ilike(f"%{value}%"))
85
+
43
86
  return self
44
87
 
88
+ def _ensure_filtered(self):
89
+ """Ensure that `filter()` has been called before using certain methods."""
90
+ if not self._filtered:
91
+ raise RuntimeError("You must call `filter()` before using this method.")
92
+
45
93
  async def all(self) -> List[ModelType]:
46
- """Return all filtered results."""
47
- query = select(self.model).filter_by(**self.filters)
94
+ """Return all results based on applied filters."""
95
+ self._ensure_filtered()
96
+
97
+ query = select(self.model)
98
+ if self.filters:
99
+ query = query.filter(and_(*self.filters))
100
+
48
101
  result = await self.session.execute(query)
49
102
  return list(result.scalars().all())
50
103
 
51
104
  async def first(self) -> Optional[ModelType]:
52
105
  """Return the first matching object, or None."""
53
- query = select(self.model).filter_by(**self.filters)
106
+ self._ensure_filtered()
107
+
108
+ query = select(self.model).filter(and_(*self.filters))
54
109
  result = await self.session.execute(query)
55
110
  return result.scalars().first()
56
111
 
57
112
  async def count(self) -> int:
58
113
  """Return the count of matching records."""
59
- query = select(self.model).filter_by(**self.filters)
114
+ self._ensure_filtered()
115
+
116
+ query = select(self.model).filter(and_(*self.filters))
60
117
  result = await self.session.execute(query)
61
118
  return len(result.scalars().all())
62
119
 
@@ -83,37 +140,35 @@ class BaseManager(Generic[ModelType]):
83
140
  raise InternalException(f"Unexpected error during creation: {str(e)}") from e
84
141
 
85
142
 
86
- async def delete(self, id: Optional[int] = None, **filters):
143
+ async def delete(self):
87
144
  """
88
145
  Delete object(s) with flexible filtering options
89
146
 
90
- :param id: Specific ID to delete
91
- :param filters: Additional filter conditions
92
147
  :return: Number of deleted records or None
93
148
  """
94
- try:
95
- if id is not None:
96
- filters['id'] = id
149
+ self._ensure_filtered()
97
150
 
98
- delete_stmt = delete(self.model).filter_by(**filters)
151
+ try:
152
+ delete_stmt = delete(self.model).where(and_(*self.filters))
99
153
  result = await self.session.execute(delete_stmt)
100
154
  await self.session.commit()
101
- return result
155
+ return result.rowcount
102
156
  except NoResultFound:
103
157
  return False
104
158
  except SQLAlchemyError as e:
105
159
  await self.session.rollback()
106
160
  raise DatabaseException(f"Delete operation failed: {str(e)}") from e
107
161
 
108
- async def bulk_delete(self, **filters) -> int:
162
+ async def bulk_delete(self) -> int:
109
163
  """
110
164
  Bulk delete with flexible filtering
111
165
 
112
- :param filters: Conditions for bulk deletion
113
166
  :return: Number of deleted records
114
167
  """
168
+ self._ensure_filtered()
169
+
115
170
  try:
116
- delete_stmt = delete(self.model).filter_by(**filters)
171
+ delete_stmt = delete(self.model).where(and_(*self.filters))
117
172
  result = await self.session.execute(delete_stmt)
118
173
  await self.session.commit()
119
174
  return result.rowcount()
File without changes
@@ -0,0 +1,9 @@
1
+ from rb_commons.configs.config import configs
2
+
3
+ class MediaUtils:
4
+
5
+ @staticmethod
6
+ def url_builder(key: str):
7
+ return "{endpoint_url}/{bucket_name}/{key}" \
8
+ .format(endpoint_url=configs.DIGITALOCEAN_S3_ENDPOINT_URL,
9
+ bucket_name=configs.DIGITALOCEAN_STORAGE_BUCKET_NAME, key=key)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rb-commons
3
- Version: 0.1.21
3
+ Version: 0.1.23
4
4
  Summary: Commons of project and simplified orm based on sqlalchemy.
5
5
  Home-page: https://github.com/RoboSell-organization/rb-commons
6
6
  Author: Abdulvoris
@@ -1,17 +1,19 @@
1
1
  rb_commons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  rb_commons/configs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- rb_commons/configs/config.py,sha256=tpqC1V9PXx88m0N5L13WqkoalPUk6SthjxgL_lmf-lE,1439
3
+ rb_commons/configs/config.py,sha256=dlsf1GDT81B_gOJkMHnnU5uHiCHiTIlvXNIdTu52E5k,1559
4
4
  rb_commons/configs/injections.py,sha256=6B1EOgIGnkWv3UrFaV9PRgG0-CJAbLu1UZ3kq-SjPVU,273
5
5
  rb_commons/http/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  rb_commons/http/exceptions.py,sha256=EGRMr1cRgiJ9Q2tkfANbf0c6-zzXf1CD6J3cmCaT_FA,1885
7
7
  rb_commons/orm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  rb_commons/orm/exceptions.py,sha256=1aMctiEwrPjyehoXVX1l6ML5ZOhmDkmBISzlTD5ey1Y,509
9
- rb_commons/orm/managers.py,sha256=2RJyiW4uUYtgE_BzOS6Z3-TdHgXE0PPcFuQYt4Ze3MQ,7755
9
+ rb_commons/orm/managers.py,sha256=aYwBAkewrw2tEEHKO_kBBnkkKR1lk8UKblr6YOMWr94,9805
10
10
  rb_commons/permissions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  rb_commons/permissions/role_permissions.py,sha256=3ZTwKclavAKSheO58fybo2uLDeTkd-iluTY7hUEjRPk,957
12
12
  rb_commons/schemes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  rb_commons/schemes/jwt.py,sha256=F66JJDhholuOPPzlKeoC6f1TL4gXg4oRUrV5yheNpyo,1675
14
- rb_commons-0.1.21.dist-info/METADATA,sha256=llsCrcIKFe3bm6GSCf5_Ooe0JHhvg1l1ExupHjP0PSc,6533
15
- rb_commons-0.1.21.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
16
- rb_commons-0.1.21.dist-info/top_level.txt,sha256=HPx_WAYo3_fbg1WCeGHsz3wPGio1ucbnrlm2lmqlJog,11
17
- rb_commons-0.1.21.dist-info/RECORD,,
14
+ rb_commons/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ rb_commons/utils/media.py,sha256=KNY_9SdRa3Rp7d3B1tZaXkhmzVa65RcS62BYwZP1bVM,332
16
+ rb_commons-0.1.23.dist-info/METADATA,sha256=CXoN6dhyHB73CL8dVDWQP68ry2SJaTa4VmmernuYV1I,6533
17
+ rb_commons-0.1.23.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
18
+ rb_commons-0.1.23.dist-info/top_level.txt,sha256=HPx_WAYo3_fbg1WCeGHsz3wPGio1ucbnrlm2lmqlJog,11
19
+ rb_commons-0.1.23.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (76.0.0)
2
+ Generator: setuptools (76.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5