Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details β†’
Socket
Book a DemoInstallSign in
Socket

dotorm

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

dotorm - npm Package Compare versions

Comparing version
2.0.6
to
2.0.7
+1
-1
dotorm/__init__.py

@@ -49,3 +49,3 @@ """

__version__ = "2.0.6"
__version__ = "2.0.7"

@@ -52,0 +52,0 @@ __all__ = [

@@ -164,3 +164,4 @@ """CRUD operations mixin."""

if sort not in store_fields:
raise ValueError(f"Invalid sort field: {sort}")
sort = store_fields[0]
# raise ValueError(f"Invalid sort field: {sort}")

@@ -199,1 +200,49 @@ fields_store_stmt = ", ".join(

return stmt, val
def build_search_count(
self: "BuilderProtocol",
filter: FilterExpression | None = None,
) -> tuple[str, tuple]:
"""
Build COUNT query with filter.
Args:
filter: Filter expression
Returns:
Tuple of (query, values)
"""
where = ""
where_values: tuple = ()
if filter:
where_clause, where_values = self.filter_parser.parse(filter)
where = f"WHERE {where_clause}"
stmt = f"SELECT COUNT(*) as count FROM {self.table} {where}"
return stmt, where_values
def build_exists(
self: "BuilderProtocol",
filter: FilterExpression | None = None,
) -> tuple[str, tuple]:
"""
Build EXISTS query with filter.
Args:
filter: Filter expression
Returns:
Tuple of (query, values)
"""
where = ""
where_values: tuple = ()
if filter:
where_clause, where_values = self.filter_parser.parse(filter)
where = f"WHERE {where_clause}"
stmt = f"SELECT 1 FROM {self.table} {where} LIMIT 1"
return stmt, where_values

@@ -8,3 +8,3 @@ """Protocol defining what mixins expect from the base class."""

if TYPE_CHECKING:
from ..orm.model import DotModel
from ..model import DotModel
from ..fields import Field

@@ -11,0 +11,0 @@ from ..components.dialect import Dialect

@@ -118,2 +118,10 @@ """

elif op in ("=", "!=", ">", "<", ">=", "<="):
# None -> IS NULL / IS NOT NULL
if value is None:
if op == "=":
return f"{field} IS NULL", ()
elif op == "!=":
return f"{field} IS NOT NULL", ()
else:
raise ValueError(f"Operator '{op}' cannot be used with None")
clause = f"{field} {op} %s"

@@ -120,0 +128,0 @@ return clause, (value,)

@@ -5,2 +5,3 @@ """

@hybridmethod - Π΄Π΅ΠΊΠΎΡ€Π°Ρ‚ΠΎΡ€ для Π³ΠΈΠ±Ρ€ΠΈΠ΄Π½Ρ‹Ρ… ΠΌΠ΅Ρ‚ΠΎΠ΄ΠΎΠ² (Ρ€Π°Π±ΠΎΡ‚Π°ΡŽΡ‚ И ΠΊΠ°ΠΊ classmethod И ΠΊΠ°ΠΊ instance).
@onchange - Π΄Π΅ΠΊΠΎΡ€Π°Ρ‚ΠΎΡ€ для ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΎΠ² измСнСния ΠΏΠΎΠ»Π΅ΠΉ.
@model - Π΄Π΅ΠΊΠΎΡ€Π°Ρ‚ΠΎΡ€ для бизнСс-ΠΌΠ΅Ρ‚ΠΎΠ΄ΠΎΠ² ΠΌΠΎΠ΄Π΅Π»ΠΈ.

@@ -297,2 +298,70 @@

# ЭкспортируСм Π΄Π΅ΠΊΠΎΡ€Π°Ρ‚ΠΎΡ€Ρ‹
__all__ = ["hybridmethod"]
__all__ = ["hybridmethod", "onchange"]
def onchange(*fields: str):
"""
Π”Π΅ΠΊΠΎΡ€Π°Ρ‚ΠΎΡ€ для рСгистрации ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΎΠ² измСнСния ΠΏΠΎΠ»Π΅ΠΉ.
ΠŸΡ€ΠΈ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΈ ΡƒΠΊΠ°Π·Π°Π½Π½Ρ‹Ρ… ΠΏΠΎΠ»Π΅ΠΉ Π½Π° Ρ„Ρ€ΠΎΠ½Ρ‚Π΅Π½Π΄Π΅
вызываСтся Π΄Π΅ΠΊΠΎΡ€ΠΈΡ€ΠΎΠ²Π°Π½Π½Ρ‹ΠΉ ΠΌΠ΅Ρ‚ΠΎΠ΄, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΌΠΎΠΆΠ΅Ρ‚ Π²Π΅Ρ€Π½ΡƒΡ‚ΡŒ значСния для
обновлСния Π΄Ρ€ΡƒΠ³ΠΈΡ… ΠΏΠΎΠ»Π΅ΠΉ Ρ„ΠΎΡ€ΠΌΡ‹.
ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ использования:
```python
from backend.base.system.dotorm.dotorm.decorators import onchange
class ChatConnector(DotModel):
@onchange('type')
async def _onchange_type(self) -> dict:
'''ВызываСтся ΠΏΡ€ΠΈ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΈ поля type'''
if self.type == 'telegram':
return {
'connector_url': 'https://api.telegram.org',
'category': 'messenger',
}
return {}
@onchange('category', 'type')
async def _onchange_category_type(self) -> dict:
'''ВызываСтся ΠΏΡ€ΠΈ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΈ category ΠΈΠ»ΠΈ type'''
# self содСрТит Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠ΅ значСния Ρ„ΠΎΡ€ΠΌΡ‹
return {'name': f'{self.category} - {self.type}'}
```
ПовСдСниС:
- ΠœΠ΅Ρ‚ΠΎΠ΄ Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ async
- self заполняСтся Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠΌΠΈ значСниями Ρ„ΠΎΡ€ΠΌΡ‹
- ΠœΠ΅Ρ‚ΠΎΠ΄ Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ dict с полями для обновлСния
- ΠŸΡƒΡΡ‚ΠΎΠΉ dict {} ΠΎΠ·Π½Π°Ρ‡Π°Π΅Ρ‚ "Π½ΠΈΡ‡Π΅Π³ΠΎ Π½Π΅ ΠΌΠ΅Π½ΡΡ‚ΡŒ"
- Π¦Π΅ΠΏΠΎΡ‡ΠΊΠΈ onchange НЕ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°ΡŽΡ‚ΡΡ (Ссли onchange мСняСт ΠΏΠΎΠ»Π΅
Ρƒ ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ³ΠΎ Ρ‚ΠΎΠΆΠ΅ Π΅ΡΡ‚ΡŒ onchange, Π²Ρ‚ΠΎΡ€ΠΎΠΉ НЕ вызываСтся)
Args:
*fields: ИмСна ΠΏΠΎΠ»Π΅ΠΉ, ΠΏΡ€ΠΈ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΈ ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… Π²Ρ‹Π·Ρ‹Π²Π°Ρ‚ΡŒ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ
Returns:
Π”Π΅ΠΊΠΎΡ€Π°Ρ‚ΠΎΡ€ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ
"""
def decorator(func: Callable[..., Coroutine[Any, Any, dict]]):
# ΠŸΠΎΠΌΠ΅Ρ‡Π°Π΅ΠΌ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΡŽ ΠΊΠ°ΠΊ onchange ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ
func._onchange_fields = fields
func._is_onchange = True
@functools.wraps(func)
async def wrapper(self, *args, **kwargs) -> dict:
result = await func(self, *args, **kwargs)
# Π“Π°Ρ€Π°Π½Ρ‚ΠΈΡ€ΡƒΠ΅ΠΌ Ρ‡Ρ‚ΠΎ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ - ΡΠ»ΠΎΠ²Π°Ρ€ΡŒ
if result is None:
return {}
return result
# ΠŸΠ΅Ρ€Π΅Π½ΠΎΡΠΈΠΌ ΠΌΠ΅Ρ‚Π°Π΄Π°Π½Π½Ρ‹Π΅ Π½Π° wrapper
wrapper._onchange_fields = fields
wrapper._is_onchange = True
return wrapper
return decorator

@@ -211,3 +211,87 @@ """ORM field definitions."""

class Selection(Char): ...
class Selection(Char):
"""
Selection field - Π²Ρ‹Π±ΠΎΡ€ ΠΈΠ· списка ΠΎΠΏΡ†ΠΈΠΉ.
Π₯ранится ΠΊΠ°ΠΊ VARCHAR, Π½ΠΎ ΠΈΠΌΠ΅Π΅Ρ‚ ΠΎΠ³Ρ€Π°Π½ΠΈΡ‡Π΅Π½Π½Ρ‹ΠΉ Π½Π°Π±ΠΎΡ€ допустимых Π·Π½Π°Ρ‡Π΅Π½ΠΈΠΉ.
ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅ Ρ‡Π΅Ρ€Π΅Π· @extend с selection_add:
# Базовая модСль
class ChatConnector(DotModel):
__table__ = "chat_connector"
type = Selection(
options=[("internal", "Internal")],
default="internal",
)
# Π Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅ ΠΈΠ· Π΄Ρ€ΡƒΠ³ΠΎΠ³ΠΎ модуля
@extend(ChatConnector)
class ChatConnectorTelegramMixin:
type = Selection(selection_add=[("telegram", "Telegram")])
Args:
options: Бписок ΠΊΠΎΡ€Ρ‚Π΅ΠΆΠ΅ΠΉ (value, label) - Π±Π°Π·ΠΎΠ²Ρ‹Π΅ ΠΎΠΏΡ†ΠΈΠΈ
selection_add: Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ ΠΎΠΏΡ†ΠΈΠΈ для Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΡ ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰Π΅Π³ΠΎ поля
default: Π—Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ
required: ΠžΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎΠ΅ ΠΏΠΎΠ»Π΅
"""
def __init__(
self,
options: list[tuple[str, str]] | None = None,
selection_add: list[tuple[str, str]] | None = None,
**kwargs
):
# Π‘Π°Π·ΠΎΠ²Ρ‹Π΅ ΠΎΠΏΡ†ΠΈΠΈ
self._base_options: list[tuple[str, str]] = options or []
# ΠžΠΏΡ†ΠΈΠΈ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½Π½Ρ‹Π΅ Ρ‡Π΅Ρ€Π΅Π· selection_add (ΠΈΠ· @extend)
self._added_options: list[tuple[str, str]] = []
# selection_add ΠΏΡ€ΠΈ ΠΈΠ½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ (для @extend)
self._selection_add = selection_add
# Для Char Π½ΡƒΠΆΠ΅Π½ max_length
if "max_length" not in kwargs:
kwargs["max_length"] = 64
super().__init__(**kwargs)
@property
def options(self) -> list[tuple[str, str]]:
"""ВсС ΠΎΠΏΡ†ΠΈΠΈ Π²ΠΊΠ»ΡŽΡ‡Π°Ρ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½Π½Ρ‹Π΅ Ρ‡Π΅Ρ€Π΅Π· extend."""
return self._base_options + self._added_options
@options.setter
def options(self, value: list[tuple[str, str]]):
"""Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ Π±Π°Π·ΠΎΠ²Ρ‹Π΅ ΠΎΠΏΡ†ΠΈΠΈ."""
self._base_options = value or []
def add_options(self, new_options: list[tuple[str, str]]) -> None:
"""
Π”ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ ΠΎΠΏΡ†ΠΈΠΈ ΠΊ полю.
Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ систСмой Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠΉ (@extend) для добавлСния
Π½ΠΎΠ²Ρ‹Ρ… Π·Π½Π°Ρ‡Π΅Π½ΠΈΠΉ Π² Selection ΠΏΠΎΠ»Π΅.
Args:
new_options: Бписок ΠΊΠΎΡ€Ρ‚Π΅ΠΆΠ΅ΠΉ (value, label)
"""
for opt in new_options:
if opt not in self._base_options and opt not in self._added_options:
self._added_options.append(opt)
def get_values(self) -> list[str]:
"""ΠŸΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ список допустимых Π·Π½Π°Ρ‡Π΅Π½ΠΈΠΉ (Π±Π΅Π· labels)."""
return [opt[0] for opt in self.options]
def get_label(self, value: str) -> str | None:
"""ΠŸΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ label для значСния."""
for opt_value, opt_label in self.options:
if opt_value == value:
return opt_label
return None
def is_selection_add(self) -> bool:
"""ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΈΡ‚ΡŒ являСтся Π»ΠΈ это Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅ΠΌ (selection_add)."""
return self._selection_add is not None

@@ -214,0 +298,0 @@

@@ -126,2 +126,4 @@ """DotModel - main ORM model class."""

__table__: ClassVar[str]
# create table in db
__auto_create__: ClassVar[bool] = True
# path name for route ednpoints CRUD

@@ -569,4 +571,8 @@ __route__: ClassVar[str]

# ΠΈΠ½Π°Ρ‡Π΅ Π²Π·ΡΡ‚ΡŒ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ ΠΈΠ»ΠΈ None
if not field.default is None:
fields_json[field_name] = field.default
if field.default is not None:
# Ссли default - callable (лямбда ΠΈΠ»ΠΈ функция), Π²Ρ‹Π·Ρ‹Π²Π°Π΅ΠΌ Π΅Ρ‘
if callable(field.default):
fields_json[field_name] = field.default()
else:
fields_json[field_name] = field.default
else:

@@ -599,3 +605,3 @@ fields_json[field_name] = None

}
for rec in field
for rec in field["data"]
]

@@ -652,4 +658,72 @@ elif mode == JsonMode.FORM:

@classmethod
def get_onchange_fields(cls) -> list[str]:
"""
ΠŸΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ список ΠΏΠΎΠ»Π΅ΠΉ Ρƒ ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… Π΅ΡΡ‚ΡŒ onchange ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΈ.
Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ Ρ„Ρ€ΠΎΠ½Ρ‚Π΅Π½Π΄ΠΎΠΌ для опрСдСлСния Π·Π° ΠΊΠ°ΠΊΠΈΠΌΠΈ полями ΡΠ»Π΅Π΄ΠΈΡ‚ΡŒ.
Returns:
Бписок ΠΈΠΌΡ‘Π½ ΠΏΠΎΠ»Π΅ΠΉ с onchange ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠ°ΠΌΠΈ
"""
fields_with_onchange = set()
for attr_name in dir(cls):
if attr_name.startswith("_"):
attr = getattr(cls, attr_name, None)
if attr and callable(attr) and hasattr(attr, "_is_onchange"):
onchange_fields = getattr(attr, "_onchange_fields", ())
fields_with_onchange.update(onchange_fields)
return list(fields_with_onchange)
@classmethod
def _get_onchange_handlers(cls, field_name: str) -> list[str]:
"""
ΠŸΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ список ΠΌΠ΅Ρ‚ΠΎΠ΄ΠΎΠ²-ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΎΠ² для ΡƒΠΊΠ°Π·Π°Π½Π½ΠΎΠ³ΠΎ поля.
Args:
field_name: Имя поля
Returns:
Бписок ΠΈΠΌΡ‘Π½ ΠΌΠ΅Ρ‚ΠΎΠ΄ΠΎΠ²-ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΎΠ²
"""
handlers = []
for attr_name in dir(cls):
if attr_name.startswith("_"):
attr = getattr(cls, attr_name, None)
if attr and callable(attr) and hasattr(attr, "_is_onchange"):
onchange_fields = getattr(attr, "_onchange_fields", ())
if field_name in onchange_fields:
handlers.append(attr_name)
return handlers
async def execute_onchange(self, field_name: str) -> dict:
"""
Π’Ρ‹ΠΏΠΎΠ»Π½ΠΈΡ‚ΡŒ всС onchange ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΈ для ΡƒΠΊΠ°Π·Π°Π½Π½ΠΎΠ³ΠΎ поля.
ΠŸΠ΅Ρ€Π΅Π΄ Π²Ρ‹Π·ΠΎΠ²ΠΎΠΌ self Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ Π·Π°ΠΏΠΎΠ»Π½Π΅Π½ Ρ‚Π΅ΠΊΡƒΡ‰ΠΈΠΌΠΈ значСниями Ρ„ΠΎΡ€ΠΌΡ‹.
Args:
field_name: Имя ΠΈΠ·ΠΌΠ΅Π½Ρ‘Π½Π½ΠΎΠ³ΠΎ поля
Returns:
ΠžΠ±ΡŠΠ΅Π΄ΠΈΠ½Ρ‘Π½Π½Ρ‹ΠΉ dict со значСниями для обновлСния Ρ„ΠΎΡ€ΠΌΡ‹
"""
result = {}
handlers = self._get_onchange_handlers(field_name)
for handler_name in handlers:
handler = getattr(self, handler_name, None)
if handler and callable(handler):
handler_result = await handler()
if handler_result:
result.update(handler_result)
return result
# Backward compatibility alias
Model = DotModel

@@ -105,3 +105,12 @@ """DDL Mixin - provides table creation functionality."""

async def __create_table__(cls, session=None):
"""ΠœΠ΅Ρ‚ΠΎΠ΄ для создания Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹ Π² Π±Π°Π·Π΅ Π΄Π°Π½Π½Ρ‹Ρ…, основанной Π½Π° Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π°Ρ… класса."""
"""ΠœΠ΅Ρ‚ΠΎΠ΄ для создания Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹ Π² Π±Π°Π·Π΅ Π΄Π°Π½Π½Ρ‹Ρ…, основанной Π½Π° Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Π°Ρ… класса.
Если __auto_create__ = False, пропускаСт созданиС Ρ‚Π°Π±Π»ΠΈΡ†Ρ‹.
Π­Ρ‚ΠΎ ΠΏΠΎΠ»Π΅Π·Π½ΠΎ для ΡΠ²ΡΠ·ΡƒΡŽΡ‰ΠΈΡ… Ρ‚Π°Π±Π»ΠΈΡ† many2many, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ ΡΠΎΠ·Π΄Π°ΡŽΡ‚ΡΡ
автоматичСски ΠΏΡ€ΠΈ создании основной ΠΌΠΎΠ΄Π΅Π»ΠΈ.
"""
# ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅ΠΌ Ρ„Π»Π°Π³ __auto_create__
if not cls.__auto_create__:
return []
session = cls._get_db_session(session)

@@ -166,2 +175,3 @@

if field.relation and isinstance(field, Many2many):
# id_column = '"id" SERIAL PRIMARY KEY'
column1 = f'"{field.column1}" INTEGER NOT NULL'

@@ -168,0 +178,0 @@ column2 = f'"{field.column2}" INTEGER NOT NULL'

@@ -39,2 +39,4 @@ """Relations ORM operations mixin."""

- search - search records with relation loading
- search_count - count records matching filter
- exists - have one record or not
- get_with_relations - get single record with relations

@@ -96,2 +98,54 @@ - update_with_relations - update record with relations

@hybridmethod
async def search_count(
self,
filter: FilterExpression | None = None,
session=None,
) -> int:
"""
Count records matching the filter.
Args:
filter: Filter expression
session: Database session
Returns:
Number of matching records
"""
cls = self.__class__
session = cls._get_db_session(session)
stmt, values = cls._builder.build_search_count(filter)
result = await session.execute(stmt, values)
if result and len(result) > 0:
return result[0].get("count", 0)
return 0
@hybridmethod
async def exists(
self,
filter: FilterExpression | None = None,
session=None,
) -> bool:
"""
Check if any record matches the filter.
More efficient than search_count for existence checks.
Args:
filter: Filter expression
session: Database session
Returns:
True if at least one record exists
"""
cls = self.__class__
session = cls._get_db_session(session)
stmt, values = cls._builder.build_exists(filter)
result = await session.execute(stmt, values)
return bool(result)
@classmethod

@@ -104,3 +158,3 @@ async def get_with_relations(

session=None,
) -> Self | None:
) -> Self:
"""Get record with relations loaded."""

@@ -126,3 +180,3 @@ if not fields:

if not record_raw:
return None
raise ValueError("Record not found")
record = cls(**record_raw[0])

@@ -129,0 +183,0 @@

@@ -32,2 +32,3 @@ """Protocols defining what ORM mixins expect from the model class."""

__table__: ClassVar[str]
__auto_create__: ClassVar[bool] = True
_pool: ClassVar[Union["aiomysql.Pool", "asyncpg.Pool"]]

@@ -34,0 +35,0 @@ _no_transaction: ClassVar[Type]

Metadata-Version: 2.4
Name: dotorm
Version: 2.0.6
Version: 2.0.7
Summary: Async Python ORM for PostgreSQL, MySQL and ClickHouse with dot-notation access

@@ -5,0 +5,0 @@ Project-URL: Homepage, https://github.com/shurshilov/dotorm

@@ -7,3 +7,3 @@ [build-system]

name = "dotorm"
version = "2.0.6"
version = "2.0.7"
description = "Async Python ORM for PostgreSQL, MySQL and ClickHouse with dot-notation access"

@@ -10,0 +10,0 @@ readme = "README.md"