moss-mop 0.1.0__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 (35) hide show
  1. moss_mop-0.1.0/PKG-INFO +10 -0
  2. moss_mop-0.1.0/README.md +249 -0
  3. moss_mop-0.1.0/mop/__init__.py +3 -0
  4. moss_mop-0.1.0/mop/conf/__init__.py +1 -0
  5. moss_mop-0.1.0/mop/conf/settings.py +133 -0
  6. moss_mop-0.1.0/mop/crud/__init__.py +2 -0
  7. moss_mop-0.1.0/mop/crud/crud.py +164 -0
  8. moss_mop-0.1.0/mop/crud/page.py +11 -0
  9. moss_mop-0.1.0/mop/db/__init__.py +3 -0
  10. moss_mop-0.1.0/mop/db/async_session.py +33 -0
  11. moss_mop-0.1.0/mop/db/base.py +12 -0
  12. moss_mop-0.1.0/mop/db/session.py +29 -0
  13. moss_mop-0.1.0/mop/entity/__init__.py +1 -0
  14. moss_mop-0.1.0/mop/entity/entity.py +36 -0
  15. moss_mop-0.1.0/mop/error/__init__.py +2 -0
  16. moss_mop-0.1.0/mop/error/biz_error.py +13 -0
  17. moss_mop-0.1.0/mop/error/err_code.py +28 -0
  18. moss_mop-0.1.0/mop/logging/__init__.py +1 -0
  19. moss_mop-0.1.0/mop/logging/logging.py +152 -0
  20. moss_mop-0.1.0/mop/response/__init__.py +1 -0
  21. moss_mop-0.1.0/mop/response/response.py +38 -0
  22. moss_mop-0.1.0/mop/snowflake.py +192 -0
  23. moss_mop-0.1.0/mop/util/__init__.py +1 -0
  24. moss_mop-0.1.0/mop/util/dates.py +5 -0
  25. moss_mop-0.1.0/mop/util/encoder.py +178 -0
  26. moss_mop-0.1.0/mop/util/files.py +27 -0
  27. moss_mop-0.1.0/mop/util/strings.py +10 -0
  28. moss_mop-0.1.0/mop/util/util.py +43 -0
  29. moss_mop-0.1.0/moss_mop.egg-info/PKG-INFO +10 -0
  30. moss_mop-0.1.0/moss_mop.egg-info/SOURCES.txt +33 -0
  31. moss_mop-0.1.0/moss_mop.egg-info/dependency_links.txt +1 -0
  32. moss_mop-0.1.0/moss_mop.egg-info/requires.txt +5 -0
  33. moss_mop-0.1.0/moss_mop.egg-info/top_level.txt +1 -0
  34. moss_mop-0.1.0/pyproject.toml +12 -0
  35. moss_mop-0.1.0/setup.cfg +4 -0
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: moss-mop
3
+ Version: 0.1.0
4
+ Summary: Lightweight FastAPI + SQLAlchemy backend toolkit with logging, CRUD, and utilities
5
+ Requires-Python: >=3.12
6
+ Requires-Dist: ascii-colors>=0.11.6
7
+ Requires-Dist: fastapi>=0.124.4
8
+ Requires-Dist: pydantic>=2.12.5
9
+ Requires-Dist: sqlalchemy>=2.0.45
10
+ Requires-Dist: structlog>=25.5.0
@@ -0,0 +1,249 @@
1
+ # MoP
2
+
3
+ MoP is a lightweight Python backend framework built on FastAPI and SQLAlchemy, providing core functionality modules for building modern web applications.
4
+
5
+ ## Features
6
+
7
+ - **Configuration Management**: Flexible configuration system with easy-to-use settings
8
+ - **Database Support**: Both synchronous and asynchronous database sessions with SQLAlchemy
9
+ - **Entity Framework**: Base entity class for consistent data modeling
10
+ - **Error Handling**: Centralized error handling with business error codes
11
+ - **Logging**: Structured logging with structlog
12
+ - **Response Formatting**: Consistent API response formatting
13
+ - **CRUD Operations**: Generic CRUD operations with pagination support
14
+ - **Utilities**: Various utility functions for dates, strings, files, and more
15
+ - **Snowflake ID Generation**: Distributed unique ID generation
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install moss-mop
21
+ ```
22
+
23
+ Or using uv:
24
+
25
+ ```bash
26
+ uv add moss-mop
27
+ ```
28
+
29
+ ## Requirements
30
+
31
+ - Python 3.12+
32
+ - FastAPI 0.124.4+
33
+ - SQLAlchemy 2.0.45+
34
+ - Pydantic 2.12.5+
35
+ - structlog 25.5.0+
36
+ - ascii-colors 0.11.6+
37
+
38
+ ## Project Structure
39
+
40
+ ```
41
+ mop/
42
+ ├── conf/ # Configuration management
43
+ ├── db/ # Database connections
44
+ ├── entity/ # Base entity class
45
+ ├── error/ # Error handling
46
+ ├── logging/ # Logging configuration
47
+ ├── response/ # Response formatting
48
+ ├── crud/ # CRUD operations
49
+ ├── util/ # Utility functions
50
+ └── snowflake.py # Snowflake ID generation
51
+ ```
52
+
53
+ ## Quick Start
54
+
55
+ ### 1. Configuration
56
+
57
+ ```python
58
+ from mop.conf import settings
59
+
60
+ # Access configuration settings
61
+ print(settings.APP_NAME)
62
+ print(settings.DATABASE_URL)
63
+ ```
64
+
65
+ ### 2. Database Setup
66
+
67
+ ```python
68
+ from mop.db import session, async_session
69
+ from sqlalchemy import select
70
+ from your_model import User
71
+
72
+ # Synchronous session
73
+ with session() as db:
74
+ users = db.execute(select(User)).scalars().all()
75
+ print(users)
76
+
77
+ # Asynchronous session
78
+ async with async_session() as db:
79
+ users = await db.execute(select(User))
80
+ users = users.scalars().all()
81
+ print(users)
82
+ ```
83
+
84
+ ### 3. Creating Models
85
+
86
+ ```python
87
+ from mop.entity import Entity
88
+ from sqlalchemy import Column, String
89
+
90
+
91
+ class User(Entity):
92
+ __tablename__ = "users"
93
+
94
+ username = Column(String(50), unique=True, index=True)
95
+ email = Column(String(100), unique=True, index=True)
96
+ ```
97
+
98
+ ### 4. Error Handling
99
+
100
+ ```python
101
+ from mop.error import BusinessError, ErrCode
102
+
103
+ # Raise business error
104
+ raise BusinessError(ErrCode.USER_NOT_FOUND, "User not found")
105
+ ```
106
+
107
+ ### 5. Logging
108
+
109
+ ```python
110
+ from mop.logging import logger
111
+
112
+ # Log messages
113
+ logger.info("User logged in", user_id=123)
114
+ logger.error("Failed to process request", error="Invalid input")
115
+ ```
116
+
117
+ ### 6. Response Formatting
118
+
119
+ ```python
120
+ from mop.response import Response, PageResponse
121
+ from fastapi import FastAPI
122
+
123
+ app = FastAPI()
124
+
125
+
126
+ @app.get("/users")
127
+ async def get_users():
128
+ users = [{"id": 1, "name": "John"}, {"id": 2, "name": "Jane"}]
129
+ return Response(data=users, message="Success")
130
+
131
+
132
+ @app.get("/users/page")
133
+ async def get_users_page():
134
+ users = [{"id": 1, "name": "John"}, {"id": 2, "name": "Jane"}]
135
+ return PageResponse(data=users, total=100, page=1, page_size=20)
136
+ ```
137
+
138
+ ### 7. CRUD Operations
139
+
140
+ ```python
141
+ from mop.crud import CRUDBase
142
+ from your_model import User, UserCreate, UserUpdate
143
+
144
+ # Create CRUD instance
145
+ user_crud = CRUDBase(User)
146
+
147
+ # Create user
148
+ user = user_crud.create(db, obj_in=UserCreate(username="test", email="test@example.com"))
149
+
150
+ # Get user by ID
151
+ user = user_crud.get(db, id=1)
152
+
153
+ # Update user
154
+ user = user_crud.update(db, db_obj=user, obj_in=UserUpdate(email="new@example.com"))
155
+
156
+ # Delete user
157
+ user_crud.remove(db, id=1)
158
+
159
+ # Get users with pagination
160
+ users, total = user_crud.get_multi(db, page=1, page_size=10)
161
+ ```
162
+
163
+ ### 8. Snowflake ID Generation
164
+
165
+ ```python
166
+ from mop.snowflake import SnowflakeGenerator
167
+
168
+ # Create generator instance
169
+ generator = SnowflakeGenerator(datacenter_id=1, worker_id=1)
170
+
171
+ # Generate unique ID
172
+ unique_id = generator.generate()
173
+ print(unique_id)
174
+ ```
175
+
176
+ ## Utilities
177
+
178
+ ### Date Utilities
179
+
180
+ ```python
181
+ from mop.util.dates import format_datetime, parse_datetime
182
+
183
+ # Format datetime
184
+ formatted = format_datetime(datetime.now())
185
+ print(formatted)
186
+
187
+ # Parse datetime
188
+ parsed = parse_datetime("2023-01-01 12:00:00")
189
+ print(parsed)
190
+ ```
191
+
192
+ ### String Utilities
193
+
194
+ ```python
195
+ from mop.util.strings import random_string, is_email_valid
196
+
197
+ # Generate random string
198
+ rand_str = random_string(10)
199
+ print(rand_str)
200
+
201
+ # Check if email is valid
202
+ is_valid = is_email_valid("test@example.com")
203
+ print(is_valid)
204
+ ```
205
+
206
+ ### File Utilities
207
+
208
+ ```python
209
+ from mop.util.files import read_file, write_file
210
+
211
+ # Read file
212
+ content = read_file("test.txt")
213
+ print(content)
214
+
215
+ # Write file
216
+ write_file("test.txt", "Hello, World!")
217
+ ```
218
+
219
+ ## Changelog
220
+
221
+ ### Version 0.1.0 (2025-12-15)
222
+
223
+ #### Added
224
+ - Initial release of MoP framework
225
+ - Configuration management module
226
+ - Database support with synchronous and asynchronous sessions
227
+ - Base entity class for data modeling
228
+ - Centralized error handling system
229
+ - Structured logging configuration
230
+ - Consistent API response formatting
231
+ - Generic CRUD operations with pagination
232
+ - Utility functions for dates, strings, files, and more
233
+ - Snowflake ID generation for distributed systems
234
+
235
+ ## Contributing
236
+
237
+ Contributions are welcome! Please feel free to submit a Pull Request.
238
+
239
+ ## License
240
+
241
+ MIT License
242
+
243
+ ## Author
244
+
245
+ - **puras** - [puras.he@gmail.com](mailto:puras.he@gmail.com)
246
+
247
+ ## GitHub
248
+
249
+ [https://github.com/puras/mop](https://github.com/puras/mop)
@@ -0,0 +1,3 @@
1
+ __version__ = "0.1.0"
2
+ __author__ = "puras"
3
+ __email__ = "puras.he@gmail.com"
@@ -0,0 +1 @@
1
+ from .settings import *
@@ -0,0 +1,133 @@
1
+ import os
2
+ from enum import Enum
3
+ from typing import List
4
+
5
+ from pydantic.v1 import BaseSettings
6
+
7
+
8
+ class Environment(str, Enum):
9
+ DEVELOPMENT = "dev"
10
+ PRODUCTION = "prod"
11
+ TEST = "test"
12
+
13
+ def get_environment() -> Environment:
14
+ match os.getenv("APP_ENV", "development").lower():
15
+ case "prod" | "production":
16
+ return Environment.PRODUCTION
17
+ case "test":
18
+ return Environment.TEST
19
+ case _:
20
+ return Environment.DEVELOPMENT
21
+
22
+ class Settings(BaseSettings):
23
+ # 基础配置
24
+ PROJECT_NAME: str = "MOSS"
25
+ PROJECT_DESCRIPTION: str = ""
26
+ VERSION: str = "0.1.0"
27
+ DEBUG: bool = True
28
+ ENVIRONMENT = get_environment()
29
+
30
+ # API配置
31
+ API_V1_STR: str = "/api/v1"
32
+ HOST: str = os.getenv("HOST", "0.0.0.0")
33
+ PORT: int = os.getenv("PORT", 8000)
34
+ WORKERS: int = 4
35
+
36
+ # 日志配置
37
+ LOG_DIR: str =os.getenv(
38
+ "LOG_DIR",
39
+ "logs"
40
+ )
41
+ LOG_LEVEL: str = os.getenv(
42
+ "LOG_LEVEL",
43
+ "INFO"
44
+ )
45
+ LOG_FORMAT: str = os.getenv(
46
+ "LOG_FORMAT",
47
+ "json"
48
+ ) # json or console
49
+
50
+ # 安全配置
51
+ ALLOWED_ORIGINS: List[str] = [
52
+ "*",
53
+ ]
54
+
55
+ # 数据库配置
56
+ SNOWFLAKE_DATACENTER_ID: int = os.getenv("SNOWFLAKE_DATACENTER_ID", 0)
57
+
58
+ DATABASE_URI: str = os.getenv(
59
+ "DATABASE_URI",
60
+ "sqlite:///./instance/moss.db" # 使用异步SQLite驱动
61
+ )
62
+
63
+ ASYNC_DATABASE_URI: str = os.getenv(
64
+ "ASYNC_DATABASE_URI",
65
+ "sqlite+aiosqlite:///./instance/moss.db" # 使用异步SQLite驱动
66
+ )
67
+ DB_TABLE_PREFIX = os.getenv(
68
+ "DB_TABLE_PREFIX",
69
+ "mo_"
70
+ )
71
+
72
+ MINIO_ENDPOINT: str = os.getenv(
73
+ "MINIO_ENDPOINT",
74
+ "http://127.0.0.1:9000"
75
+ )
76
+
77
+ MINIO_SHARE_ENDPOINT: str = os.getenv(
78
+ "MINIO_SHARE_ENDPOINT",
79
+ "http://127.0.0.1:9000/minio"
80
+ )
81
+
82
+ MINIO_ACCESS_KEY: str = os.getenv(
83
+ "MINIO_ACCESS_KEY",
84
+ ""
85
+ )
86
+
87
+ MINIO_SECRET_KEY: str = os.getenv(
88
+ "MINIO_SECRET_KEY",
89
+ ""
90
+ )
91
+
92
+ MINIO_DEFAULT_BUCKET: str = os.getenv(
93
+ "MINIO_DEFAULT_BUCKET",
94
+ "mo"
95
+ )
96
+
97
+ STORAGE_TYPE: str = os.getenv(
98
+ "STORAGE_TYPE",
99
+ "local"
100
+ )
101
+
102
+ OPENDAL_SCHEME: str = os.getenv(
103
+ "OPENDAL_SCHEME",
104
+ "fs"
105
+ )
106
+ OPENDAL_FS_ROOT: str = os.getenv(
107
+ "OPENDAL_FS_ROOT",
108
+ "storage"
109
+ )
110
+
111
+ TEMP_DIR: str = os.getenv(
112
+ "TEMP_DIR",
113
+ "temp"
114
+ )
115
+
116
+ AUTH_SECRET_KEY: str = os.getenv(
117
+ "AUTH_SECRET_KEY",
118
+ "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
119
+ )
120
+ AUTH_SECRET_ALGORITHM: str = os.getenv(
121
+ "AUTH_SECRET_ALGORITHM",
122
+ "HS256"
123
+ )
124
+ AUTH_ACCESS_TOKEN_EXPIRE: int = os.getenv(
125
+ "AUTH_ACCESS_TOKEN_EXPIRE",
126
+ 30
127
+ )
128
+
129
+ class Config:
130
+ env_file = ".env"
131
+ case_sensitive = True
132
+
133
+ settings = Settings()
@@ -0,0 +1,2 @@
1
+ from .page import *
2
+ from .crud import *
@@ -0,0 +1,164 @@
1
+ from typing import Generic, TypeVar
2
+
3
+ from sqlalchemy import select, func
4
+
5
+ from .page import ListSlice
6
+ from mop.db import AsyncSessionLocal, SessionLocal
7
+ from mop.entity import BaseEntity
8
+ from mop.error import DATA_NOT_FOUND, BizError
9
+
10
+ T = TypeVar("T", bound=BaseEntity)
11
+
12
+
13
+ class CrudBiz(Generic[T]):
14
+ def __init__(self, model: type[T]):
15
+ self.model = model
16
+
17
+ def sync_find_all(self, **kwargs):
18
+ filters = [self.model.deleted == False]
19
+
20
+ for key, value in kwargs.items():
21
+ if hasattr(self.model, key):
22
+ filters.append(getattr(self.model, key) == value)
23
+
24
+ with SessionLocal() as db:
25
+ try:
26
+ ret = db.execute(select(self.model).where(*filters))
27
+ items = ret.scalars().all()
28
+ return items
29
+ finally:
30
+ db.close()
31
+
32
+ def sync_find_by_id(self, item_id: str):
33
+ filters = [self.model.id == item_id, self.model.deleted == False]
34
+ with SessionLocal() as db:
35
+ try:
36
+ ret = db.execute(select(self.model).where(*filters))
37
+ ret = ret.scalars().first()
38
+ if not ret:
39
+ raise BizError.init(DATA_NOT_FOUND)
40
+ return ret
41
+ finally:
42
+ db.close()
43
+
44
+ async def find_all(self, **kwargs):
45
+ filters = [self.model.deleted == False]
46
+
47
+ for key, value in kwargs.items():
48
+ if hasattr(self.model, key):
49
+ filters.append(getattr(self.model, key) == value)
50
+
51
+ async with AsyncSessionLocal() as db:
52
+ try:
53
+ ret = await db.execute(select(self.model).where(*filters))
54
+ items = ret.scalars().all()
55
+ return items
56
+ finally:
57
+ await db.close()
58
+
59
+ async def count_by(self, **kwargs):
60
+ filters = [self.model.deleted == False]
61
+ for key, value in kwargs.items():
62
+ if hasattr(self.model, key):
63
+ filters.append(getattr(self.model, key) == value)
64
+ primary_column = self.model.__mapper__.primary_key[0]
65
+ async with AsyncSessionLocal() as db:
66
+ try:
67
+ cnt = await db.execute(select(func.count(primary_column)).where(*filters))
68
+ return cnt.scalar()
69
+ finally:
70
+ await db.close()
71
+
72
+ async def _count_by(self, db: AsyncSessionLocal, *filters):
73
+ primary_column = self.model.__mapper__.primary_key[0]
74
+ cnt = await db.execute(select(func.count(primary_column)).where(*filters))
75
+ return cnt.scalar()
76
+
77
+ async def find_by(self, page_num: int = 1, page_size: int = 100, **kwargs):
78
+ filters = [self.model.deleted == False]
79
+
80
+ for key, value in kwargs.items():
81
+ if hasattr(self.model, key):
82
+ filters.append(getattr(self.model, key) == value)
83
+
84
+ skip = (page_num - 1) * page_size
85
+ async with AsyncSessionLocal() as db:
86
+ try:
87
+ print(filters)
88
+ total = await self._count_by(db, *filters)
89
+ ret = await db.execute(
90
+ select(self.model).where(*filters).offset(skip).limit(page_size)
91
+ )
92
+ items = ret.scalars().all()
93
+ return ListSlice(
94
+ items=items, total=total, page_num=page_num, page_size=page_size
95
+ )
96
+ finally:
97
+ await db.close()
98
+
99
+ async def find_by_id(self, item_id: str):
100
+ filters = [self.model.id == item_id, self.model.deleted == False]
101
+ async with AsyncSessionLocal() as db:
102
+ try:
103
+ ret = await db.execute(select(self.model).where(*filters))
104
+ ret = ret.scalars().first()
105
+ if not ret:
106
+ raise BizError.init(DATA_NOT_FOUND)
107
+ return ret
108
+ finally:
109
+ await db.close()
110
+
111
+ async def find_one(self, **kwargs):
112
+ filters = [self.model.deleted == False]
113
+
114
+ for key, value in kwargs.items():
115
+ if hasattr(self.model, key):
116
+ filters.append(getattr(self.model, key) == value)
117
+
118
+ async with AsyncSessionLocal() as db:
119
+ try:
120
+ ret = await db.execute(select(self.model).where(*filters))
121
+ item = ret.scalars().first()
122
+ return item
123
+ finally:
124
+ await db.close()
125
+
126
+ async def create(self, **kwargs):
127
+ item = self.model(**kwargs)
128
+ item.default_created()
129
+ async with AsyncSessionLocal() as db:
130
+ try:
131
+ db.add(item)
132
+ await db.commit()
133
+ await db.refresh(item)
134
+ return item
135
+ finally:
136
+ await db.close()
137
+
138
+ async def update(self, item_id: str, **kwargs):
139
+ item = await self.find_by_id(item_id)
140
+
141
+ other = self.model(**kwargs)
142
+ item.update_attrs(other)
143
+
144
+ async with AsyncSessionLocal() as db:
145
+ try:
146
+ db.add(item)
147
+ await db.commit()
148
+ await db.refresh(item)
149
+ return item
150
+ finally:
151
+ await db.close()
152
+
153
+ async def delete(self, item_id: str) -> bool:
154
+ item = await self.find_by_id(item_id)
155
+ item.default_deleted()
156
+ async with AsyncSessionLocal() as db:
157
+ try:
158
+ db.add(item)
159
+ await db.commit()
160
+ await db.refresh(item)
161
+ return True
162
+ finally:
163
+ await db.close()
164
+ return False
@@ -0,0 +1,11 @@
1
+ from typing import List, TypeVar, Generic
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+ T = TypeVar("T")
6
+
7
+ class ListSlice(BaseModel, Generic[T]):
8
+ items: List[T] = Field(description="当前页的数据集合")
9
+ total: int = Field(description="总记录数")
10
+ page_num: int = Field(1, description="当前页面, 默认为1")
11
+ page_size: int = Field(10, description="每页的数据量,默认为10")
@@ -0,0 +1,3 @@
1
+ from .base import *
2
+ from .session import *
3
+ from .async_session import *
@@ -0,0 +1,33 @@
1
+ from typing import AsyncGenerator, Annotated
2
+
3
+ from fastapi import Depends
4
+ from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
5
+ from sqlalchemy.orm import sessionmaker
6
+
7
+ from mop.conf import settings
8
+
9
+ async_engine = create_async_engine(
10
+ settings.ASYNC_DATABASE_URI,
11
+ echo=settings.DEBUG
12
+ )
13
+
14
+ AsyncSessionLocal = sessionmaker(
15
+ async_engine,
16
+ class_=AsyncSession,
17
+ expire_on_commit=False,
18
+ autocommit=False,
19
+ autoflush=False,
20
+ )
21
+
22
+ async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
23
+ """
24
+ 获取数据库会话依赖
25
+ :return:
26
+ """
27
+ async with AsyncSessionLocal() as session:
28
+ try:
29
+ yield session
30
+ finally:
31
+ await session.close()
32
+
33
+ AsyncSessionDep = Annotated[AsyncSession, Depends(get_async_session)]
@@ -0,0 +1,12 @@
1
+ from sqlalchemy.orm import declared_attr, declarative_base
2
+
3
+ from mop.conf import settings
4
+ from mop.util.strings import to_underline
5
+
6
+
7
+ class Base:
8
+ @declared_attr
9
+ def __tablename__(self):
10
+ return f"{settings.DB_TABLE_PREFIX}{to_underline(self.__name__)}"
11
+
12
+ Base = declarative_base(cls=Base)
@@ -0,0 +1,29 @@
1
+ from typing import Annotated
2
+
3
+ from fastapi import Depends
4
+ from sqlalchemy import create_engine
5
+ from sqlalchemy.orm import sessionmaker, Session
6
+
7
+ from mop.conf import settings
8
+
9
+ engine = create_engine(
10
+ settings.DATABASE_URI,
11
+ echo=settings.DEBUG
12
+ )
13
+
14
+ SessionLocal = sessionmaker(
15
+ engine,
16
+ class_=Session,
17
+ expire_on_commit=False,
18
+ autocommit=False,
19
+ autoflush=False,
20
+ )
21
+
22
+ def get_session():
23
+ with SessionLocal() as session:
24
+ try:
25
+ yield session
26
+ finally:
27
+ session.close()
28
+
29
+ SessionDep = Annotated[Session, Depends(get_session)]
@@ -0,0 +1 @@
1
+ from .entity import *
@@ -0,0 +1,36 @@
1
+ from datetime import datetime
2
+
3
+ from sqlalchemy import Column, String, Boolean, DateTime, text, Integer
4
+
5
+ from mop.db import Base
6
+ from mop.snowflake import IDWorker
7
+
8
+
9
+ class BaseEntity(Base):
10
+ __abstract__ = True
11
+ id = Column(Integer, primary_key=True, index=True)
12
+ tenant_id = Column(String(32), nullable=False)
13
+ deleted = Column(Boolean, nullable=False, server_default=text("false"))
14
+ created_by = Column(String(32), nullable=True)
15
+ created_at = Column(DateTime, nullable=True, default=datetime.now)
16
+ updated_by = Column(String(32), nullable=True)
17
+ updated_at = Column(DateTime, nullable=True, default=datetime.now)
18
+ deleted_by = Column(String(32), nullable=True)
19
+ deleted_at = Column(DateTime, nullable=True)
20
+
21
+ def default_created(self):
22
+ self.id = IDWorker.gen_id()
23
+ self.tenant_id = '000000'
24
+
25
+ def update_attrs(self, other):
26
+ for attr in other.__dict__.keys():
27
+ if hasattr(self, attr) and attr != 'id' and attr != '_sa_instance_state':
28
+ setattr(self, attr, getattr(other, attr))
29
+ self.updated_at = datetime.now()
30
+
31
+ def default_deleted(self):
32
+ self.deleted = True
33
+ self.deleted_at = datetime.now()
34
+
35
+
36
+
@@ -0,0 +1,2 @@
1
+ from .err_code import *
2
+ from .biz_error import *