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.
Files changed (81) hide show
  1. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/PKG-INFO +1 -1
  2. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/mapper/impl/base_mapper_impl.py +1 -1
  3. fastlib_py-0.2.8/fastlib/service/base_service.py +187 -0
  4. fastlib_py-0.2.8/fastlib/service/impl/__init__.py +0 -0
  5. fastlib_py-0.2.8/fastlib/service/impl/base_service_impl.py +304 -0
  6. fastlib_py-0.2.8/fastlib/session/__init__.py +6 -0
  7. fastlib_py-0.2.8/fastlib/stream/__init__.py +1 -0
  8. fastlib_py-0.2.8/fastlib/stream/enums.py +36 -0
  9. fastlib_py-0.2.8/fastlib/stream/event.py +101 -0
  10. fastlib_py-0.2.8/fastlib/stream/handler.py +187 -0
  11. fastlib_py-0.2.8/fastlib/stream/schema.py +140 -0
  12. fastlib_py-0.2.8/fastlib/stream/sse.py +272 -0
  13. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib_py.egg-info/PKG-INFO +1 -1
  14. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib_py.egg-info/SOURCES.txt +7 -0
  15. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/pyproject.toml +1 -1
  16. fastlib_py-0.2.7/fastlib/service/base_service.py +0 -75
  17. fastlib_py-0.2.7/fastlib/service/impl/base_service_impl.py +0 -79
  18. fastlib_py-0.2.7/fastlib/session/__init__.py +0 -5
  19. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/LICENSE +0 -0
  20. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/README.md +0 -0
  21. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/__init__.py +0 -0
  22. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/cache/__init__.py +0 -0
  23. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/cache/base.py +0 -0
  24. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/cache/disk_cache.py +0 -0
  25. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/cache/manager.py +0 -0
  26. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/cache/redis_cache.py +0 -0
  27. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/config/__init__.py +0 -0
  28. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/config/base.py +0 -0
  29. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/config/database_config.py +0 -0
  30. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/config/loader.py +0 -0
  31. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/config/manager.py +0 -0
  32. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/config/registry.py +0 -0
  33. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/config/security_config.py +0 -0
  34. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/config/server_config.py +0 -0
  35. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/config/utils.py +0 -0
  36. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/constant/__init__.py +0 -0
  37. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/constant/constant.py +0 -0
  38. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/contextvars.py +0 -0
  39. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/enums.py +0 -0
  40. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/exception/__init__.py +0 -0
  41. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/exception/base.py +0 -0
  42. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/exception/exception_handler.py +0 -0
  43. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/exception/exception_manager.py +0 -0
  44. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/i18n/__init__.py +0 -0
  45. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/i18n/manager.py +0 -0
  46. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/i18n/messages.py +0 -0
  47. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/i18n/types.py +0 -0
  48. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/i18n/utils.py +0 -0
  49. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/logging/__init__.py +0 -0
  50. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/logging/config.py +0 -0
  51. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/logging/handlers.py +0 -0
  52. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/mapper/__init__.py +0 -0
  53. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/mapper/base_mapper.py +0 -0
  54. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/mapper/impl/__init__.py +0 -0
  55. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/middleware/__init__.py +0 -0
  56. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/middleware/db_session.py +0 -0
  57. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/middleware/i18n.py +0 -0
  58. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/middleware/jwt.py +0 -0
  59. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/migrate.py +0 -0
  60. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/openapi.py +0 -0
  61. /fastlib_py-0.2.7/fastlib/service/__init__.py → /fastlib_py-0.2.8/fastlib/py.typed +0 -0
  62. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/request.py +0 -0
  63. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/response.py +0 -0
  64. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/router.py +0 -0
  65. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/schema.py +0 -0
  66. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/security/__init__.py +0 -0
  67. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/security/security.py +0 -0
  68. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/security/utils.py +0 -0
  69. {fastlib_py-0.2.7/fastlib/service/impl → fastlib_py-0.2.8/fastlib/service}/__init__.py +0 -0
  70. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/session/db_engine.py +0 -0
  71. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/utils/__init__.py +0 -0
  72. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/utils/excel_util.py +0 -0
  73. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/utils/file_util.py +0 -0
  74. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/utils/http_util.py +0 -0
  75. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/utils/ip_util.py +0 -0
  76. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/utils/snowflake_util.py +0 -0
  77. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib/utils/str_util.py +0 -0
  78. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib_py.egg-info/dependency_links.txt +0 -0
  79. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib_py.egg-info/requires.txt +0 -0
  80. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/fastlib_py.egg-info/top_level.txt +0 -0
  81. {fastlib_py-0.2.7 → fastlib_py-0.2.8}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastlib-py
3
- Version: 0.2.7
3
+ Version: 0.2.8
4
4
  Summary: Fastlib, high performance, easy to use, fast to code, ready for production
5
5
  Author-email: tyvekZhang <tyvekzhang@gmail.com>
6
6
  Maintainer-email: tyvekZhang <tyvekzhang@gmail.com>
@@ -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.execute(stmt)
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,6 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Export session symbols"""
3
+
4
+ from .db_engine import get_async_engine
5
+
6
+ __all__ = [get_async_engine]
@@ -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()