fastlib-py 0.2.7__tar.gz → 0.2.8__tar.gz
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.
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/PKG-INFO +1 -1
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/mapper/impl/base_mapper_impl.py +1 -1
- fastlib_py-0.2.8/fastlib/service/base_service.py +187 -0
- fastlib_py-0.2.8/fastlib/service/impl/__init__.py +0 -0
- fastlib_py-0.2.8/fastlib/service/impl/base_service_impl.py +304 -0
- fastlib_py-0.2.8/fastlib/session/__init__.py +6 -0
- fastlib_py-0.2.8/fastlib/stream/__init__.py +1 -0
- fastlib_py-0.2.8/fastlib/stream/enums.py +36 -0
- fastlib_py-0.2.8/fastlib/stream/event.py +101 -0
- fastlib_py-0.2.8/fastlib/stream/handler.py +187 -0
- fastlib_py-0.2.8/fastlib/stream/schema.py +140 -0
- fastlib_py-0.2.8/fastlib/stream/sse.py +272 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib_py.egg-info/PKG-INFO +1 -1
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib_py.egg-info/SOURCES.txt +7 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/pyproject.toml +1 -1
- fastlib_py-0.2.7/fastlib/service/base_service.py +0 -75
- fastlib_py-0.2.7/fastlib/service/impl/base_service_impl.py +0 -79
- fastlib_py-0.2.7/fastlib/session/__init__.py +0 -5
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/LICENSE +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/README.md +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/__init__.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/cache/__init__.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/cache/base.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/cache/disk_cache.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/cache/manager.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/cache/redis_cache.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/config/__init__.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/config/base.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/config/database_config.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/config/loader.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/config/manager.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/config/registry.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/config/security_config.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/config/server_config.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/config/utils.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/constant/__init__.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/constant/constant.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/contextvars.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/enums.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/exception/__init__.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/exception/base.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/exception/exception_handler.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/exception/exception_manager.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/i18n/__init__.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/i18n/manager.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/i18n/messages.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/i18n/types.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/i18n/utils.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/logging/__init__.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/logging/config.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/logging/handlers.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/mapper/__init__.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/mapper/base_mapper.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/mapper/impl/__init__.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/middleware/__init__.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/middleware/db_session.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/middleware/i18n.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/middleware/jwt.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/migrate.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/openapi.py +0 -0
- /fastlib_py-0.2.7/fastlib/service/__init__.py → /fastlib_py-0.2.8/fastlib/py.typed +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/request.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/response.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/router.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/schema.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/security/__init__.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/security/security.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/security/utils.py +0 -0
- {fastlib_py-0.2.7/fastlib/service/impl → fastlib_py-0.2.8/fastlib/service}/__init__.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/session/db_engine.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/utils/__init__.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/utils/excel_util.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/utils/file_util.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/utils/http_util.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/utils/ip_util.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/utils/snowflake_util.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/utils/str_util.py +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib_py.egg-info/dependency_links.txt +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib_py.egg-info/requires.txt +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib_py.egg-info/top_level.txt +0 -0
- {fastlib_py-0.2.7 → fastlib_py-0.2.8}/setup.cfg +0 -0
|
@@ -550,7 +550,7 @@ class SqlModelMapper(BaseMapper, Generic[ModelType]):
|
|
|
550
550
|
|
|
551
551
|
# Query children for the current level
|
|
552
552
|
stmt = select(self.model).where(self.model.parent_id.in_(parent_ids))
|
|
553
|
-
result = await db_session.
|
|
553
|
+
result = await db_session.exec(stmt)
|
|
554
554
|
children = result.scalars().all()
|
|
555
555
|
|
|
556
556
|
# Convert ORM children to SchemaType instances
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Abstract service with common database operations."""
|
|
3
|
+
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from typing import Any, Generic, Optional, TypeVar
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
from sqlmodel import SQLModel
|
|
9
|
+
from sqlmodel.ext.asyncio.session import AsyncSession
|
|
10
|
+
|
|
11
|
+
from fastlib.schema import SortItem
|
|
12
|
+
|
|
13
|
+
IDType = TypeVar("IDType", int, str)
|
|
14
|
+
ModelType = TypeVar("ModelType", bound=SQLModel)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BaseService(Generic[ModelType], ABC):
|
|
18
|
+
"""Base service interface with common CRUD operations"""
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
async def save(
|
|
22
|
+
self, data: BaseModel, db_session: Optional[AsyncSession] = None
|
|
23
|
+
) -> BaseModel:
|
|
24
|
+
"""Insert a single record"""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
async def save_batch(
|
|
29
|
+
self, data_list: list[BaseModel], db_session: Optional[AsyncSession] = None
|
|
30
|
+
) -> int:
|
|
31
|
+
"""Batch insert records"""
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
async def save_or_update(
|
|
36
|
+
self, data: BaseModel, db_session: Optional[AsyncSession] = None
|
|
37
|
+
) -> bool:
|
|
38
|
+
"""Insert or update a record"""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
async def save_or_update_batch(
|
|
43
|
+
self, data_list: list[BaseModel], db_session: Optional[AsyncSession] = None
|
|
44
|
+
) -> bool:
|
|
45
|
+
"""Batch insert or update records"""
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
@abstractmethod
|
|
49
|
+
async def remove_by_id(
|
|
50
|
+
self, id: IDType, db_session: Optional[AsyncSession] = None
|
|
51
|
+
) -> bool:
|
|
52
|
+
"""Delete record by ID"""
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
@abstractmethod
|
|
56
|
+
async def remove_by_ids(
|
|
57
|
+
self, ids: list[IDType], db_session: Optional[AsyncSession] = None
|
|
58
|
+
) -> bool:
|
|
59
|
+
"""Batch delete records by IDs"""
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
@abstractmethod
|
|
63
|
+
async def remove(self, db_session: Optional[AsyncSession] = None, **kwargs) -> bool:
|
|
64
|
+
"""Delete records by conditions"""
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
@abstractmethod
|
|
68
|
+
async def update_by_id(
|
|
69
|
+
self, data: BaseModel, db_session: Optional[AsyncSession] = None
|
|
70
|
+
) -> bool:
|
|
71
|
+
"""Update record by ID"""
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
@abstractmethod
|
|
75
|
+
async def update_batch_by_ids(
|
|
76
|
+
self,
|
|
77
|
+
ids: list[IDType],
|
|
78
|
+
data: dict[str, Any],
|
|
79
|
+
db_session: Optional[AsyncSession] = None,
|
|
80
|
+
) -> bool:
|
|
81
|
+
"""Batch update records by IDs"""
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
@abstractmethod
|
|
85
|
+
async def batch_update(
|
|
86
|
+
self, data_list: list[BaseModel], db_session: Optional[AsyncSession] = None
|
|
87
|
+
) -> bool:
|
|
88
|
+
"""Batch update records"""
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
@abstractmethod
|
|
92
|
+
async def update(
|
|
93
|
+
self, data: BaseModel, db_session: Optional[AsyncSession] = None, **kwargs
|
|
94
|
+
) -> bool:
|
|
95
|
+
"""Update records by conditions"""
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
@abstractmethod
|
|
99
|
+
async def get_by_id(
|
|
100
|
+
self,
|
|
101
|
+
id: IDType,
|
|
102
|
+
fields: Optional[list[str]] = None,
|
|
103
|
+
db_session: Optional[AsyncSession] = None,
|
|
104
|
+
) -> Optional[BaseModel]:
|
|
105
|
+
"""Get record by ID"""
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
@abstractmethod
|
|
109
|
+
async def get_one(
|
|
110
|
+
self, db_session: Optional[AsyncSession] = None, **kwargs
|
|
111
|
+
) -> Optional[BaseModel]:
|
|
112
|
+
"""Get one record by conditions"""
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
@abstractmethod
|
|
116
|
+
async def list_by_ids(
|
|
117
|
+
self,
|
|
118
|
+
ids: list[IDType],
|
|
119
|
+
fields: Optional[list[str]] = None,
|
|
120
|
+
db_session: Optional[AsyncSession] = None,
|
|
121
|
+
) -> list[BaseModel]:
|
|
122
|
+
"""Get records by IDs"""
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
@abstractmethod
|
|
126
|
+
async def list_all(
|
|
127
|
+
self,
|
|
128
|
+
fields: Optional[list[str]] = None,
|
|
129
|
+
db_session: Optional[AsyncSession] = None,
|
|
130
|
+
**kwargs,
|
|
131
|
+
) -> list[BaseModel]:
|
|
132
|
+
"""Get all records with optional filtering"""
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
@abstractmethod
|
|
136
|
+
async def list(
|
|
137
|
+
self, db_session: Optional[AsyncSession] = None, **kwargs
|
|
138
|
+
) -> list[BaseModel]:
|
|
139
|
+
"""Get records by conditions"""
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
@abstractmethod
|
|
143
|
+
async def page(
|
|
144
|
+
self,
|
|
145
|
+
current: int = 1,
|
|
146
|
+
page_size: int = 100,
|
|
147
|
+
count: bool = True,
|
|
148
|
+
fields: Optional[list[str]] = None,
|
|
149
|
+
db_session: Optional[AsyncSession] = None,
|
|
150
|
+
**kwargs,
|
|
151
|
+
) -> tuple[list[BaseModel], int]:
|
|
152
|
+
"""Paginated query"""
|
|
153
|
+
pass
|
|
154
|
+
|
|
155
|
+
@abstractmethod
|
|
156
|
+
async def page_ordered(
|
|
157
|
+
self,
|
|
158
|
+
current: int = 1,
|
|
159
|
+
page_size: int = 100,
|
|
160
|
+
count: bool = True,
|
|
161
|
+
sort_list: Optional[list[SortItem]] = None,
|
|
162
|
+
fields: Optional[list[str]] = None,
|
|
163
|
+
db_session: Optional[AsyncSession] = None,
|
|
164
|
+
**kwargs,
|
|
165
|
+
) -> tuple[list[BaseModel], int]:
|
|
166
|
+
"""Paginated query with ordering"""
|
|
167
|
+
pass
|
|
168
|
+
|
|
169
|
+
@abstractmethod
|
|
170
|
+
async def count(self, db_session: Optional[AsyncSession] = None, **kwargs) -> int:
|
|
171
|
+
"""Count records by conditions"""
|
|
172
|
+
pass
|
|
173
|
+
|
|
174
|
+
@abstractmethod
|
|
175
|
+
async def exists(self, db_session: Optional[AsyncSession] = None, **kwargs) -> bool:
|
|
176
|
+
"""Check if records exist by conditions"""
|
|
177
|
+
pass
|
|
178
|
+
|
|
179
|
+
@abstractmethod
|
|
180
|
+
async def get_children_tree(
|
|
181
|
+
self,
|
|
182
|
+
parent_data: list[BaseModel],
|
|
183
|
+
max_level: int = 5,
|
|
184
|
+
db_session: Optional[AsyncSession] = None,
|
|
185
|
+
) -> list[BaseModel]:
|
|
186
|
+
"""Get children tree recursively"""
|
|
187
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Base service implementation with transaction support"""
|
|
3
|
+
|
|
4
|
+
from typing import Any, Generic, Optional, TypeVar
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
from sqlmodel import SQLModel
|
|
8
|
+
from sqlmodel.ext.asyncio.session import AsyncSession
|
|
9
|
+
|
|
10
|
+
from fastlib.mapper.base_mapper import BaseMapper
|
|
11
|
+
from fastlib.schema import SortItem
|
|
12
|
+
from fastlib.service.base_service import BaseService
|
|
13
|
+
|
|
14
|
+
IDType = TypeVar("IDType", int, str)
|
|
15
|
+
ModelType = TypeVar("ModelType", bound=SQLModel)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BaseServiceImpl(
|
|
19
|
+
BaseService[ModelType],
|
|
20
|
+
Generic[ModelType],
|
|
21
|
+
):
|
|
22
|
+
"""Base service implementation with common CRUD operations and transaction support"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
mapper: BaseMapper,
|
|
27
|
+
model: type[ModelType],
|
|
28
|
+
):
|
|
29
|
+
self.mapper = mapper
|
|
30
|
+
self.model = model
|
|
31
|
+
|
|
32
|
+
async def save(
|
|
33
|
+
self, data: BaseModel, db_session: Optional[AsyncSession] = None
|
|
34
|
+
) -> BaseModel:
|
|
35
|
+
"""Insert a single record"""
|
|
36
|
+
model_data = self.model(**data.model_dump())
|
|
37
|
+
saved_data = await self.mapper.insert(data=model_data, db_session=db_session)
|
|
38
|
+
return BaseModel.model_validate(saved_data)
|
|
39
|
+
|
|
40
|
+
async def save_batch(
|
|
41
|
+
self, data_list: list[BaseModel], db_session: Optional[AsyncSession] = None
|
|
42
|
+
) -> int:
|
|
43
|
+
"""Batch insert records"""
|
|
44
|
+
model_list = [self.model(**data.model_dump()) for data in data_list]
|
|
45
|
+
return await self.mapper.batch_insert(
|
|
46
|
+
data_list=model_list, db_session=db_session
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
async def save_or_update(
|
|
50
|
+
self, data: BaseModel, db_session: Optional[AsyncSession] = None
|
|
51
|
+
) -> bool:
|
|
52
|
+
"""Insert or update a record"""
|
|
53
|
+
# Check if data exists (assuming data has id field)
|
|
54
|
+
if hasattr(data, "id") and data.id:
|
|
55
|
+
existing = await self.mapper.select_by_id(id=data.id, db_session=db_session)
|
|
56
|
+
if existing:
|
|
57
|
+
# Update existing
|
|
58
|
+
model_data = self.model(**data.model_dump())
|
|
59
|
+
count = await self.mapper.update_by_id(
|
|
60
|
+
data=model_data, db_session=db_session
|
|
61
|
+
)
|
|
62
|
+
return count > 0
|
|
63
|
+
# Insert new
|
|
64
|
+
model_data = self.model(**data.model_dump())
|
|
65
|
+
saved_data = await self.mapper.insert(data=model_data, db_session=db_session)
|
|
66
|
+
return saved_data is not None
|
|
67
|
+
|
|
68
|
+
async def save_or_update_batch(
|
|
69
|
+
self, data_list: list[BaseModel], db_session: Optional[AsyncSession] = None
|
|
70
|
+
) -> bool:
|
|
71
|
+
"""Batch insert or update records"""
|
|
72
|
+
# Separate inserts and updates
|
|
73
|
+
to_insert = []
|
|
74
|
+
to_update = []
|
|
75
|
+
|
|
76
|
+
for data in data_list:
|
|
77
|
+
if hasattr(data, "id") and data.id:
|
|
78
|
+
existing = await self.mapper.select_by_id(
|
|
79
|
+
id=data.id, db_session=db_session
|
|
80
|
+
)
|
|
81
|
+
if existing:
|
|
82
|
+
to_update.append(data)
|
|
83
|
+
continue
|
|
84
|
+
to_insert.append(data)
|
|
85
|
+
|
|
86
|
+
# Execute batch operations
|
|
87
|
+
if to_insert:
|
|
88
|
+
insert_models = [self.model(**data.model_dump()) for data in to_insert]
|
|
89
|
+
await self.mapper.batch_insert(
|
|
90
|
+
data_list=insert_models, db_session=db_session
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if to_update:
|
|
94
|
+
for data in to_update:
|
|
95
|
+
model_data = self.model(**data.model_dump())
|
|
96
|
+
await self.mapper.update_by_id(data=model_data, db_session=db_session)
|
|
97
|
+
|
|
98
|
+
return True
|
|
99
|
+
|
|
100
|
+
async def remove_by_id(
|
|
101
|
+
self, id: IDType, db_session: Optional[AsyncSession] = None
|
|
102
|
+
) -> bool:
|
|
103
|
+
"""Delete record by ID"""
|
|
104
|
+
count = await self.mapper.delete_by_id(id=id, db_session=db_session)
|
|
105
|
+
return count > 0
|
|
106
|
+
|
|
107
|
+
async def remove_by_ids(
|
|
108
|
+
self, ids: list[IDType], db_session: Optional[AsyncSession] = None
|
|
109
|
+
) -> bool:
|
|
110
|
+
"""Batch delete records by IDs"""
|
|
111
|
+
count = await self.mapper.batch_delete_by_ids(ids=ids, db_session=db_session)
|
|
112
|
+
return count > 0
|
|
113
|
+
|
|
114
|
+
async def remove(self, db_session: Optional[AsyncSession] = None, **kwargs) -> bool:
|
|
115
|
+
"""Delete records by conditions"""
|
|
116
|
+
# This would need a more complex implementation based on your filter system
|
|
117
|
+
# For now, we'll use a simple approach - get IDs first then delete
|
|
118
|
+
records, _ = await self.mapper.select_by_page(db_session=db_session, **kwargs)
|
|
119
|
+
if records:
|
|
120
|
+
ids = [record.id for record in records]
|
|
121
|
+
return await self.remove_by_ids(ids, db_session=db_session)
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
async def update_by_id(
|
|
125
|
+
self, data: BaseModel, db_session: Optional[AsyncSession] = None
|
|
126
|
+
) -> bool:
|
|
127
|
+
"""Update record by ID"""
|
|
128
|
+
model_data = self.model(**data.model_dump())
|
|
129
|
+
count = await self.mapper.update_by_id(data=model_data, db_session=db_session)
|
|
130
|
+
return count > 0
|
|
131
|
+
|
|
132
|
+
async def update_batch_by_ids(
|
|
133
|
+
self,
|
|
134
|
+
ids: list[IDType],
|
|
135
|
+
data: dict[str, Any],
|
|
136
|
+
db_session: Optional[AsyncSession] = None,
|
|
137
|
+
) -> bool:
|
|
138
|
+
"""Batch update records by IDs"""
|
|
139
|
+
count = await self.mapper.batch_update_by_ids(
|
|
140
|
+
ids=ids, data=data, db_session=db_session
|
|
141
|
+
)
|
|
142
|
+
return count > 0
|
|
143
|
+
|
|
144
|
+
async def batch_update(
|
|
145
|
+
self, data_list: list[BaseModel], db_session: Optional[AsyncSession] = None
|
|
146
|
+
) -> bool:
|
|
147
|
+
"""Batch update records"""
|
|
148
|
+
model_list = [self.model(**data.model_dump()) for data in data_list]
|
|
149
|
+
count = await self.mapper.batch_update(
|
|
150
|
+
data_list=model_list, db_session=db_session
|
|
151
|
+
)
|
|
152
|
+
return count > 0
|
|
153
|
+
|
|
154
|
+
async def update(
|
|
155
|
+
self, data: BaseModel, db_session: Optional[AsyncSession] = None, **kwargs
|
|
156
|
+
) -> bool:
|
|
157
|
+
"""Update records by conditions"""
|
|
158
|
+
# Get records matching conditions
|
|
159
|
+
records = await self.list(db_session=db_session, **kwargs)
|
|
160
|
+
if not records:
|
|
161
|
+
return False
|
|
162
|
+
|
|
163
|
+
# Update each record
|
|
164
|
+
success_count = 0
|
|
165
|
+
for record in records:
|
|
166
|
+
# Merge update data with existing record
|
|
167
|
+
update_data = data.model_dump(exclude_unset=True)
|
|
168
|
+
for key, value in update_data.items():
|
|
169
|
+
setattr(record, key, value)
|
|
170
|
+
|
|
171
|
+
# Convert to model and update
|
|
172
|
+
model_data = self.model(**record.model_dump())
|
|
173
|
+
count = await self.mapper.update_by_id(
|
|
174
|
+
data=model_data, db_session=db_session
|
|
175
|
+
)
|
|
176
|
+
if count > 0:
|
|
177
|
+
success_count += 1
|
|
178
|
+
|
|
179
|
+
return success_count > 0
|
|
180
|
+
|
|
181
|
+
async def get_by_id(
|
|
182
|
+
self,
|
|
183
|
+
id: IDType,
|
|
184
|
+
fields: Optional[list[str]] = None,
|
|
185
|
+
db_session: Optional[AsyncSession] = None,
|
|
186
|
+
) -> Optional[BaseModel]:
|
|
187
|
+
"""Get record by ID"""
|
|
188
|
+
result = await self.mapper.select_by_id(
|
|
189
|
+
id=id, fields=fields, db_session=db_session
|
|
190
|
+
)
|
|
191
|
+
if result:
|
|
192
|
+
return BaseModel.model_validate(result)
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
async def get_one(
|
|
196
|
+
self, db_session: Optional[AsyncSession] = None, **kwargs
|
|
197
|
+
) -> Optional[BaseModel]:
|
|
198
|
+
"""Get one record by conditions"""
|
|
199
|
+
records = await self.list(db_session=db_session, **kwargs)
|
|
200
|
+
return records[0] if records else None
|
|
201
|
+
|
|
202
|
+
async def list_by_ids(
|
|
203
|
+
self,
|
|
204
|
+
ids: list[IDType],
|
|
205
|
+
fields: Optional[list[str]] = None,
|
|
206
|
+
db_session: Optional[AsyncSession] = None,
|
|
207
|
+
) -> list[BaseModel]:
|
|
208
|
+
"""Get records by IDs"""
|
|
209
|
+
results = await self.mapper.select_by_ids(
|
|
210
|
+
ids=ids, fields=fields, db_session=db_session
|
|
211
|
+
)
|
|
212
|
+
return [BaseModel.model_validate(result) for result in results]
|
|
213
|
+
|
|
214
|
+
async def list_all(
|
|
215
|
+
self,
|
|
216
|
+
fields: Optional[list[str]] = None,
|
|
217
|
+
db_session: Optional[AsyncSession] = None,
|
|
218
|
+
**kwargs,
|
|
219
|
+
) -> list[BaseModel]:
|
|
220
|
+
"""Get all records with optional filtering"""
|
|
221
|
+
# Use a large page size to get all records
|
|
222
|
+
records, _ = await self.mapper.select_by_page(
|
|
223
|
+
current=1,
|
|
224
|
+
page_size=10000, # Adjust based on your needs
|
|
225
|
+
count=False,
|
|
226
|
+
fields=fields,
|
|
227
|
+
db_session=db_session,
|
|
228
|
+
**kwargs,
|
|
229
|
+
)
|
|
230
|
+
return [BaseModel.model_validate(record) for record in records]
|
|
231
|
+
|
|
232
|
+
async def list(
|
|
233
|
+
self, db_session: Optional[AsyncSession] = None, **kwargs
|
|
234
|
+
) -> list[BaseModel]:
|
|
235
|
+
"""Get records by conditions"""
|
|
236
|
+
return await self.list_all(db_session=db_session, **kwargs)
|
|
237
|
+
|
|
238
|
+
async def page(
|
|
239
|
+
self,
|
|
240
|
+
current: int = 1,
|
|
241
|
+
page_size: int = 100,
|
|
242
|
+
count: bool = True,
|
|
243
|
+
fields: Optional[list[str]] = None,
|
|
244
|
+
db_session: Optional[AsyncSession] = None,
|
|
245
|
+
**kwargs,
|
|
246
|
+
) -> tuple[list[BaseModel], int]:
|
|
247
|
+
"""Paginated query"""
|
|
248
|
+
records, total = await self.mapper.select_by_page(
|
|
249
|
+
current=current,
|
|
250
|
+
page_size=page_size,
|
|
251
|
+
count=count,
|
|
252
|
+
fields=fields,
|
|
253
|
+
db_session=db_session,
|
|
254
|
+
**kwargs,
|
|
255
|
+
)
|
|
256
|
+
return [BaseModel.model_validate(record) for record in records], total
|
|
257
|
+
|
|
258
|
+
async def page_ordered(
|
|
259
|
+
self,
|
|
260
|
+
current: int = 1,
|
|
261
|
+
page_size: int = 100,
|
|
262
|
+
count: bool = True,
|
|
263
|
+
sort_list: Optional[list[SortItem]] = None,
|
|
264
|
+
fields: Optional[list[str]] = None,
|
|
265
|
+
db_session: Optional[AsyncSession] = None,
|
|
266
|
+
**kwargs,
|
|
267
|
+
) -> tuple[list[BaseModel], int]:
|
|
268
|
+
"""Paginated query with ordering"""
|
|
269
|
+
records, total = await self.mapper.select_by_ordered_page(
|
|
270
|
+
current=current,
|
|
271
|
+
page_size=page_size,
|
|
272
|
+
count=count,
|
|
273
|
+
sort_list=sort_list,
|
|
274
|
+
fields=fields,
|
|
275
|
+
db_session=db_session,
|
|
276
|
+
**kwargs,
|
|
277
|
+
)
|
|
278
|
+
return [BaseModel.model_validate(record) for record in records], total
|
|
279
|
+
|
|
280
|
+
async def count(self, db_session: Optional[AsyncSession] = None, **kwargs) -> int:
|
|
281
|
+
"""Count records by conditions"""
|
|
282
|
+
_, total = await self.mapper.select_by_page(
|
|
283
|
+
current=1, page_size=1, count=True, db_session=db_session, **kwargs
|
|
284
|
+
)
|
|
285
|
+
return total
|
|
286
|
+
|
|
287
|
+
async def exists(self, db_session: Optional[AsyncSession] = None, **kwargs) -> bool:
|
|
288
|
+
"""Check if records exist by conditions"""
|
|
289
|
+
count = await self.count(db_session=db_session, **kwargs)
|
|
290
|
+
return count > 0
|
|
291
|
+
|
|
292
|
+
async def get_children_tree(
|
|
293
|
+
self,
|
|
294
|
+
parent_data: list[BaseModel],
|
|
295
|
+
max_level: int = 5,
|
|
296
|
+
db_session: Optional[AsyncSession] = None,
|
|
297
|
+
) -> list[BaseModel]:
|
|
298
|
+
"""Get children tree recursively"""
|
|
299
|
+
results = await self.mapper.get_children_recursively(
|
|
300
|
+
parent_data=parent_data,
|
|
301
|
+
max_level=max_level,
|
|
302
|
+
db_session=db_session,
|
|
303
|
+
)
|
|
304
|
+
return [BaseModel.model_validate(result) for result in results]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""
|
|
3
|
+
Enums for stream module
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from enum import Enum
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MessageRole(str, Enum):
|
|
10
|
+
"""Message role enumeration"""
|
|
11
|
+
|
|
12
|
+
SYSTEM = "system"
|
|
13
|
+
USER = "user"
|
|
14
|
+
ASSISTANT = "assistant"
|
|
15
|
+
FUNCTION = "function"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MessageStatus(str, Enum):
|
|
19
|
+
"""Message status enumeration"""
|
|
20
|
+
|
|
21
|
+
CREATED = "created"
|
|
22
|
+
RUNNING = "running"
|
|
23
|
+
COMPLETED = "completed"
|
|
24
|
+
FAILED = "failed"
|
|
25
|
+
CANCELLED = "cancelled"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class StreamEventType(str, Enum):
|
|
29
|
+
"""Stream event type"""
|
|
30
|
+
|
|
31
|
+
START = "start"
|
|
32
|
+
CONTENT = "content"
|
|
33
|
+
FUNCTION_CALL = "function_call"
|
|
34
|
+
ERROR = "error"
|
|
35
|
+
END = "end"
|
|
36
|
+
METADATA = "metadata"
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""
|
|
3
|
+
Copy from https://github.com/sysid/sse-starlette/blob/main/sse_starlette/event.py
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import io
|
|
7
|
+
import json
|
|
8
|
+
import re
|
|
9
|
+
from typing import Any, Optional, Union
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ServerSentEvent:
|
|
13
|
+
"""
|
|
14
|
+
Helper class to format data for Server-Sent Events (SSE).
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
_LINE_SEP_EXPR = re.compile(r"\r\n|\r|\n")
|
|
18
|
+
DEFAULT_SEPARATOR = "\r\n"
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
data: Optional[Any] = None,
|
|
23
|
+
*,
|
|
24
|
+
event: Optional[str] = None,
|
|
25
|
+
id: Optional[str] = None,
|
|
26
|
+
retry: Optional[int] = None,
|
|
27
|
+
comment: Optional[str] = None,
|
|
28
|
+
sep: Optional[str] = None,
|
|
29
|
+
) -> None:
|
|
30
|
+
self.data = data
|
|
31
|
+
self.event = event
|
|
32
|
+
self.id = id
|
|
33
|
+
self.retry = retry
|
|
34
|
+
self.comment = comment
|
|
35
|
+
self._sep = sep if sep is not None else self.DEFAULT_SEPARATOR
|
|
36
|
+
|
|
37
|
+
def encode(self) -> bytes:
|
|
38
|
+
buffer = io.StringIO()
|
|
39
|
+
if self.comment is not None:
|
|
40
|
+
for chunk in self._LINE_SEP_EXPR.split(str(self.comment)):
|
|
41
|
+
buffer.write(f": {chunk}{self._sep}")
|
|
42
|
+
|
|
43
|
+
if self.id is not None:
|
|
44
|
+
# Clean newlines in the event id
|
|
45
|
+
buffer.write("id: " + self._LINE_SEP_EXPR.sub("", self.id) + self._sep)
|
|
46
|
+
|
|
47
|
+
if self.event is not None:
|
|
48
|
+
# Clean newlines in the event name
|
|
49
|
+
buffer.write(
|
|
50
|
+
"event: " + self._LINE_SEP_EXPR.sub("", self.event) + self._sep
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if self.data is not None:
|
|
54
|
+
# Break multi-line data into multiple data: lines
|
|
55
|
+
for chunk in self._LINE_SEP_EXPR.split(str(self.data)):
|
|
56
|
+
buffer.write(f"data: {chunk}{self._sep}")
|
|
57
|
+
|
|
58
|
+
if self.retry is not None:
|
|
59
|
+
if not isinstance(self.retry, int):
|
|
60
|
+
raise TypeError("retry argument must be int")
|
|
61
|
+
buffer.write(f"retry: {self.retry}{self._sep}")
|
|
62
|
+
|
|
63
|
+
buffer.write(self._sep)
|
|
64
|
+
return buffer.getvalue().encode("utf-8")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class JSONServerSentEvent(ServerSentEvent):
|
|
68
|
+
"""
|
|
69
|
+
Helper class to format JSON data for Server-Sent Events (SSE).
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
data: Optional[Any] = None,
|
|
75
|
+
*args,
|
|
76
|
+
**kwargs,
|
|
77
|
+
) -> None:
|
|
78
|
+
super().__init__(
|
|
79
|
+
json.dumps(
|
|
80
|
+
data,
|
|
81
|
+
ensure_ascii=False,
|
|
82
|
+
allow_nan=False,
|
|
83
|
+
indent=None,
|
|
84
|
+
separators=(",", ":"),
|
|
85
|
+
)
|
|
86
|
+
if data is not None
|
|
87
|
+
else None,
|
|
88
|
+
*args,
|
|
89
|
+
**kwargs,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def ensure_bytes(data: Union[bytes, dict, ServerSentEvent, Any], sep: str) -> bytes:
|
|
94
|
+
if isinstance(data, bytes):
|
|
95
|
+
return data
|
|
96
|
+
if isinstance(data, ServerSentEvent):
|
|
97
|
+
return data.encode()
|
|
98
|
+
if isinstance(data, dict):
|
|
99
|
+
data["sep"] = sep
|
|
100
|
+
return ServerSentEvent(**data).encode()
|
|
101
|
+
return ServerSentEvent(str(data), sep=sep).encode()
|