sqliter-py 0.16.0__py3-none-any.whl → 0.17.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.
- sqliter/exceptions.py +16 -0
- sqliter/helpers.py +27 -0
- sqliter/model/model.py +7 -29
- sqliter/orm/__init__.py +2 -1
- sqliter/orm/m2m.py +784 -0
- sqliter/orm/model.py +70 -5
- sqliter/orm/registry.py +273 -2
- sqliter/sqliter.py +41 -0
- sqliter/tui/demos/orm.py +79 -2
- {sqliter_py-0.16.0.dist-info → sqliter_py-0.17.0.dist-info}/METADATA +6 -8
- {sqliter_py-0.16.0.dist-info → sqliter_py-0.17.0.dist-info}/RECORD +13 -12
- {sqliter_py-0.16.0.dist-info → sqliter_py-0.17.0.dist-info}/WHEEL +0 -0
- {sqliter_py-0.16.0.dist-info → sqliter_py-0.17.0.dist-info}/entry_points.txt +0 -0
sqliter/exceptions.py
CHANGED
|
@@ -209,3 +209,19 @@ class InvalidRelationshipError(SqliterError):
|
|
|
209
209
|
"Invalid relationship path '{}': field '{}' is not a valid "
|
|
210
210
|
"foreign key relationship on model {}"
|
|
211
211
|
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class ManyToManyError(SqliterError):
|
|
215
|
+
"""Base exception for many-to-many relationship errors."""
|
|
216
|
+
|
|
217
|
+
message_template = "Many-to-many error: {}"
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class ManyToManyIntegrityError(ManyToManyError):
|
|
221
|
+
"""Raised when a M2M operation fails due to missing context or pk.
|
|
222
|
+
|
|
223
|
+
This error occurs when attempting to use a M2M relationship without
|
|
224
|
+
a database context or on an unsaved instance (no primary key).
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
message_template = "Many-to-many integrity error: {}"
|
sqliter/helpers.py
CHANGED
|
@@ -9,11 +9,38 @@ to database schema translation.
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
11
|
import datetime
|
|
12
|
+
import re
|
|
12
13
|
from typing import Union
|
|
13
14
|
|
|
14
15
|
from sqliter.constants import SQLITE_TYPE_MAPPING
|
|
15
16
|
|
|
16
17
|
|
|
18
|
+
def validate_table_name(table_name: str) -> str:
|
|
19
|
+
"""Validate that a table name contains only safe characters.
|
|
20
|
+
|
|
21
|
+
Table names must contain only alphanumeric characters and underscores,
|
|
22
|
+
and must start with a letter or underscore. This prevents SQL injection
|
|
23
|
+
through malicious table names.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
table_name: The table name to validate.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
The validated table name.
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
ValueError: If the table name contains invalid characters.
|
|
33
|
+
"""
|
|
34
|
+
if not re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", table_name):
|
|
35
|
+
msg = (
|
|
36
|
+
f"Invalid table name '{table_name}'. "
|
|
37
|
+
"Table names must start with a letter or underscore and "
|
|
38
|
+
"contain only letters, numbers, and underscores."
|
|
39
|
+
)
|
|
40
|
+
raise ValueError(msg)
|
|
41
|
+
return table_name
|
|
42
|
+
|
|
43
|
+
|
|
17
44
|
def infer_sqlite_type(field_type: Union[type, None]) -> str:
|
|
18
45
|
"""Infer the SQLite column type based on the Python type.
|
|
19
46
|
|
sqliter/model/model.py
CHANGED
|
@@ -26,7 +26,11 @@ from typing import (
|
|
|
26
26
|
from pydantic import BaseModel, ConfigDict, Field
|
|
27
27
|
from typing_extensions import Self
|
|
28
28
|
|
|
29
|
-
from sqliter.helpers import
|
|
29
|
+
from sqliter.helpers import (
|
|
30
|
+
from_unix_timestamp,
|
|
31
|
+
to_unix_timestamp,
|
|
32
|
+
validate_table_name,
|
|
33
|
+
)
|
|
30
34
|
|
|
31
35
|
|
|
32
36
|
class SerializableField(Protocol):
|
|
@@ -123,32 +127,6 @@ class BaseDBModel(BaseModel):
|
|
|
123
127
|
|
|
124
128
|
return cast("Self", cls.model_construct(**converted_obj))
|
|
125
129
|
|
|
126
|
-
@staticmethod
|
|
127
|
-
def _validate_table_name(table_name: str) -> str:
|
|
128
|
-
"""Validate that a table name contains only safe characters.
|
|
129
|
-
|
|
130
|
-
Table names must contain only alphanumeric characters and underscores,
|
|
131
|
-
and must start with a letter or underscore. This prevents SQL injection
|
|
132
|
-
through malicious table names.
|
|
133
|
-
|
|
134
|
-
Args:
|
|
135
|
-
table_name: The table name to validate.
|
|
136
|
-
|
|
137
|
-
Returns:
|
|
138
|
-
The validated table name.
|
|
139
|
-
|
|
140
|
-
Raises:
|
|
141
|
-
ValueError: If the table name contains invalid characters.
|
|
142
|
-
"""
|
|
143
|
-
if not re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", table_name):
|
|
144
|
-
msg = (
|
|
145
|
-
f"Invalid table name '{table_name}'. "
|
|
146
|
-
"Table names must start with a letter or underscore and "
|
|
147
|
-
"contain only letters, numbers, and underscores."
|
|
148
|
-
)
|
|
149
|
-
raise ValueError(msg)
|
|
150
|
-
return table_name
|
|
151
|
-
|
|
152
130
|
@classmethod
|
|
153
131
|
def get_table_name(cls) -> str:
|
|
154
132
|
"""Get the database table name for the model.
|
|
@@ -171,7 +149,7 @@ class BaseDBModel(BaseModel):
|
|
|
171
149
|
table_name: str | None = getattr(cls.Meta, "table_name", None)
|
|
172
150
|
if table_name is not None:
|
|
173
151
|
# Validate custom table names
|
|
174
|
-
return
|
|
152
|
+
return validate_table_name(table_name)
|
|
175
153
|
|
|
176
154
|
# Get class name and remove 'Model' suffix if present
|
|
177
155
|
class_name = cls.__name__.removesuffix("Model")
|
|
@@ -194,7 +172,7 @@ class BaseDBModel(BaseModel):
|
|
|
194
172
|
)
|
|
195
173
|
|
|
196
174
|
# Validate auto-generated table names (should always pass)
|
|
197
|
-
return
|
|
175
|
+
return validate_table_name(table_name)
|
|
198
176
|
|
|
199
177
|
@classmethod
|
|
200
178
|
def get_primary_key(cls) -> str:
|
sqliter/orm/__init__.py
CHANGED
|
@@ -10,7 +10,8 @@ Users can choose between modes via import:
|
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
from sqliter.orm.foreign_key import ForeignKey
|
|
13
|
+
from sqliter.orm.m2m import ManyToMany
|
|
13
14
|
from sqliter.orm.model import BaseDBModel
|
|
14
15
|
from sqliter.orm.registry import ModelRegistry
|
|
15
16
|
|
|
16
|
-
__all__ = ["BaseDBModel", "ForeignKey", "ModelRegistry"]
|
|
17
|
+
__all__ = ["BaseDBModel", "ForeignKey", "ManyToMany", "ModelRegistry"]
|