async-easy-model 0.2.9__py3-none-any.whl → 0.3.0__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.
- async_easy_model/__init__.py +1 -1
- async_easy_model/model.py +73 -5
- {async_easy_model-0.2.9.dist-info → async_easy_model-0.3.0.dist-info}/METADATA +2 -1
- async_easy_model-0.3.0.dist-info/RECORD +11 -0
- async_easy_model-0.2.9.dist-info/RECORD +0 -11
- {async_easy_model-0.2.9.dist-info → async_easy_model-0.3.0.dist-info}/WHEEL +0 -0
- {async_easy_model-0.2.9.dist-info → async_easy_model-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {async_easy_model-0.2.9.dist-info → async_easy_model-0.3.0.dist-info}/top_level.txt +0 -0
async_easy_model/__init__.py
CHANGED
@@ -12,7 +12,7 @@ from typing import Optional, Any
|
|
12
12
|
from .model import EasyModel, init_db, db_config
|
13
13
|
from sqlmodel import Field, Relationship as SQLModelRelationship
|
14
14
|
|
15
|
-
__version__ = "0.
|
15
|
+
__version__ = "0.3.0"
|
16
16
|
__all__ = ["EasyModel", "init_db", "db_config", "Field", "Relationship", "Relation", "enable_auto_relationships", "disable_auto_relationships", "process_auto_relationships", "MigrationManager", "check_and_migrate_models", "ModelVisualizer"]
|
17
17
|
|
18
18
|
# Create a more user-friendly Relationship function
|
async_easy_model/model.py
CHANGED
@@ -14,6 +14,9 @@ import re
|
|
14
14
|
|
15
15
|
T = TypeVar("T", bound="EasyModel")
|
16
16
|
|
17
|
+
# Global database configuration instance (forward declaration)
|
18
|
+
db_config = None
|
19
|
+
|
17
20
|
class DatabaseConfig:
|
18
21
|
_engine = None
|
19
22
|
_session_maker = None
|
@@ -107,13 +110,74 @@ class DatabaseConfig:
|
|
107
110
|
# Global database configuration instance.
|
108
111
|
db_config = DatabaseConfig()
|
109
112
|
|
113
|
+
def _normalize_datetime_for_db(value: Any) -> Any:
|
114
|
+
"""
|
115
|
+
Normalize datetime values for database compatibility.
|
116
|
+
|
117
|
+
For PostgreSQL with TIMESTAMP WITHOUT TIME ZONE columns, converts timezone-aware
|
118
|
+
datetimes to timezone-naive UTC datetimes. For other databases, returns the value unchanged.
|
119
|
+
|
120
|
+
Args:
|
121
|
+
value: The value to potentially normalize
|
122
|
+
|
123
|
+
Returns:
|
124
|
+
The normalized value
|
125
|
+
"""
|
126
|
+
global db_config
|
127
|
+
|
128
|
+
# Only process datetime objects
|
129
|
+
if not isinstance(value, datetime):
|
130
|
+
return value
|
131
|
+
|
132
|
+
# Only normalize for PostgreSQL
|
133
|
+
if db_config and db_config.db_type == "postgresql":
|
134
|
+
# If the datetime is timezone-aware, convert to UTC and make naive
|
135
|
+
if value.tzinfo is not None:
|
136
|
+
return value.astimezone(tz.utc).replace(tzinfo=None)
|
137
|
+
|
138
|
+
return value
|
139
|
+
|
140
|
+
def _normalize_data_for_db(data: Dict[str, Any]) -> Dict[str, Any]:
|
141
|
+
"""
|
142
|
+
Normalize all datetime values in a data dictionary for database compatibility.
|
143
|
+
|
144
|
+
Args:
|
145
|
+
data: Dictionary containing field values
|
146
|
+
|
147
|
+
Returns:
|
148
|
+
Dictionary with normalized datetime values
|
149
|
+
"""
|
150
|
+
normalized_data = {}
|
151
|
+
for key, value in data.items():
|
152
|
+
normalized_data[key] = _normalize_datetime_for_db(value)
|
153
|
+
return normalized_data
|
154
|
+
|
155
|
+
def _get_normalized_datetime() -> datetime:
|
156
|
+
"""
|
157
|
+
Get a datetime that's appropriate for the current database backend.
|
158
|
+
|
159
|
+
Returns:
|
160
|
+
For PostgreSQL: timezone-naive UTC datetime
|
161
|
+
For SQLite: timezone-aware UTC datetime (for backward compatibility)
|
162
|
+
"""
|
163
|
+
global db_config
|
164
|
+
|
165
|
+
utc_now = datetime.now(tz.utc)
|
166
|
+
|
167
|
+
# For PostgreSQL, return timezone-naive datetime
|
168
|
+
if db_config and db_config.db_type == "postgresql":
|
169
|
+
return utc_now.replace(tzinfo=None)
|
170
|
+
|
171
|
+
# For SQLite and others, return timezone-aware datetime (backward compatibility)
|
172
|
+
return utc_now
|
173
|
+
|
110
174
|
class EasyModel(SQLModel):
|
111
175
|
"""
|
112
176
|
Base model class providing common async database operations.
|
113
177
|
"""
|
114
178
|
id: Optional[int] = Field(default=None, primary_key=True)
|
115
|
-
created_at: Optional[datetime] = Field(default_factory=
|
116
|
-
updated_at: Optional[datetime] = Field(default_factory=
|
179
|
+
created_at: Optional[datetime] = Field(default_factory=_get_normalized_datetime)
|
180
|
+
updated_at: Optional[datetime] = Field(default_factory=_get_normalized_datetime)
|
117
181
|
|
118
182
|
# Default table args with extend_existing=True to ensure all subclasses can redefine tables
|
119
183
|
__table_args__ = {"extend_existing": True}
|
@@ -437,6 +501,9 @@ class EasyModel(SQLModel):
|
|
437
501
|
try:
|
438
502
|
processed_data = await cls._process_relationships_for_insert(session, data)
|
439
503
|
|
504
|
+
# Normalize datetime values for database compatibility
|
505
|
+
processed_data = _normalize_data_for_db(processed_data)
|
506
|
+
|
440
507
|
# Create the model instance
|
441
508
|
obj = cls(**processed_data)
|
442
509
|
session.add(obj)
|
@@ -769,9 +836,10 @@ class EasyModel(SQLModel):
|
|
769
836
|
if existing:
|
770
837
|
raise ValueError(f"Cannot update {field_name} to '{new_value}': value already exists")
|
771
838
|
|
772
|
-
# Apply the updates
|
839
|
+
# Apply the updates with datetime normalization
|
773
840
|
for key, value in data.items():
|
774
|
-
|
841
|
+
normalized_value = _normalize_datetime_for_db(value)
|
842
|
+
setattr(record, key, normalized_value)
|
775
843
|
|
776
844
|
# Process many-to-many relationships if any
|
777
845
|
for rel_name, rel_data in many_to_many_data.items():
|
@@ -1383,7 +1451,7 @@ class EasyModel(SQLModel):
|
|
1383
1451
|
def _update_updated_at(session, flush_context, instances):
|
1384
1452
|
for instance in session.dirty:
|
1385
1453
|
if isinstance(instance, EasyModel) and hasattr(instance, "updated_at"):
|
1386
|
-
instance.updated_at =
|
1454
|
+
instance.updated_at = _get_normalized_datetime()
|
1387
1455
|
|
1388
1456
|
async def init_db(migrate: bool = True, model_classes: List[Type[SQLModel]] = None):
|
1389
1457
|
"""
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: async-easy-model
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.3.0
|
4
4
|
Summary: A simplified SQLModel-based ORM for async database operations
|
5
5
|
Home-page: https://github.com/puntorigen/easy-model
|
6
6
|
Author: Pablo Schaffner
|
@@ -59,6 +59,7 @@ A simplified SQLModel-based ORM for async database operations in Python. async-e
|
|
59
59
|
- 🛠️ Built on top of SQLModel and SQLAlchemy for robust performance
|
60
60
|
- 📝 Type hints for better IDE support
|
61
61
|
- 🕒 Automatic `id`, `created_at` and `updated_at` fields provided by default
|
62
|
+
- ⏰ **PostgreSQL DateTime Compatibility**: Automatic timezone-aware to timezone-naive datetime conversion for PostgreSQL TIMESTAMP WITHOUT TIME ZONE columns
|
62
63
|
- 🔄 Automatic schema migrations for evolving database models
|
63
64
|
- 📊 Visualization of database schema using Mermaid ER diagrams
|
64
65
|
|
@@ -0,0 +1,11 @@
|
|
1
|
+
async_easy_model/__init__.py,sha256=IXheLaQx-dpGPL6oBEaaIftZDihlFB7bwzEWfMlZ4Ts,1921
|
2
|
+
async_easy_model/auto_relationships.py,sha256=V2LAzNi7y-keFk4C_m-byVRM-k_7nL5HEy9Ig3nEdq8,27756
|
3
|
+
async_easy_model/migrations.py,sha256=rYDGCGlruSugAmPfdIF2-uhyG6UvC_2qbF3BXJ084qI,17803
|
4
|
+
async_easy_model/model.py,sha256=XxlFOYiX8HT1B6PBB3RubuvB5j4aYKEyO-sPA202Y2U,66508
|
5
|
+
async_easy_model/relationships.py,sha256=vR5BsJpGaDcecCcNlg9-ouZfxFXFQv5kOyiXhKp_T7A,3286
|
6
|
+
async_easy_model/visualization.py,sha256=RVCdc8j3uUQe-zy3jXju_yhA13qJ8KWVbQ5fQyjyqkA,29973
|
7
|
+
async_easy_model-0.3.0.dist-info/licenses/LICENSE,sha256=uwDkl6oHbRltW7vYKNc4doJyhtwhyrSNFFlPpKATwLE,1072
|
8
|
+
async_easy_model-0.3.0.dist-info/METADATA,sha256=0PEppZL9WiSl1uKDqlC-f-l9rrrM-x5HVcRWBcRgsJU,13047
|
9
|
+
async_easy_model-0.3.0.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
|
10
|
+
async_easy_model-0.3.0.dist-info/top_level.txt,sha256=e5_47sGmJnyxz2msfwU6C316EqmrSd9RGIYwZyWx68E,17
|
11
|
+
async_easy_model-0.3.0.dist-info/RECORD,,
|
@@ -1,11 +0,0 @@
|
|
1
|
-
async_easy_model/__init__.py,sha256=eGTq3OypupyVvLe2Zyn3ZFBeuU4v8EXW-XQ047vUMoM,1921
|
2
|
-
async_easy_model/auto_relationships.py,sha256=V2LAzNi7y-keFk4C_m-byVRM-k_7nL5HEy9Ig3nEdq8,27756
|
3
|
-
async_easy_model/migrations.py,sha256=rYDGCGlruSugAmPfdIF2-uhyG6UvC_2qbF3BXJ084qI,17803
|
4
|
-
async_easy_model/model.py,sha256=pBWRAVhazZuI_rBOnILPttiCF_ZEomWBJGDmfdFp8Nk,64243
|
5
|
-
async_easy_model/relationships.py,sha256=vR5BsJpGaDcecCcNlg9-ouZfxFXFQv5kOyiXhKp_T7A,3286
|
6
|
-
async_easy_model/visualization.py,sha256=RVCdc8j3uUQe-zy3jXju_yhA13qJ8KWVbQ5fQyjyqkA,29973
|
7
|
-
async_easy_model-0.2.9.dist-info/licenses/LICENSE,sha256=uwDkl6oHbRltW7vYKNc4doJyhtwhyrSNFFlPpKATwLE,1072
|
8
|
-
async_easy_model-0.2.9.dist-info/METADATA,sha256=iVRM1qe6DfdFUSa5FYYES-oXgkKScm7m2BVClhlH-k8,12888
|
9
|
-
async_easy_model-0.2.9.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
|
10
|
-
async_easy_model-0.2.9.dist-info/top_level.txt,sha256=e5_47sGmJnyxz2msfwU6C316EqmrSd9RGIYwZyWx68E,17
|
11
|
-
async_easy_model-0.2.9.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|