surrealdb-orm 0.1.3__py3-none-any.whl → 0.1.4__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.

Potentially problematic release.


This version of surrealdb-orm might be problematic. Click here for more details.

surreal_orm/__init__.py CHANGED
@@ -1,6 +1,12 @@
1
- from .model_base import BaseSurrealModel
1
+ from .model_base import BaseSurrealModel, SurrealConfigDict
2
2
  from .connection_manager import SurrealDBConnectionManager
3
3
  from .query_set import QuerySet
4
4
  from .enum import OrderBy
5
5
 
6
- __all__ = ["SurrealDBConnectionManager", "BaseSurrealModel", "QuerySet", "OrderBy"]
6
+ __all__ = [
7
+ "SurrealDBConnectionManager",
8
+ "BaseSurrealModel",
9
+ "QuerySet",
10
+ "OrderBy",
11
+ "SurrealConfigDict",
12
+ ]
surreal_orm/model_base.py CHANGED
@@ -1,129 +1,132 @@
1
- from typing import Any, Type, Self
2
- from pydantic import BaseModel, create_model, ConfigDict
1
+ from typing import Any, Self
2
+ from pydantic import BaseModel, ConfigDict, model_validator
3
3
  from .connection_manager import SurrealDBConnectionManager
4
4
  from surrealdb import RecordID, SurrealDbError
5
5
 
6
- import warnings
7
6
  import logging
8
7
 
9
- warnings.filterwarnings("ignore", message="fields may not start with an underscore", category=RuntimeWarning)
10
8
 
11
9
  logger = logging.getLogger(__name__)
12
10
 
13
11
 
12
+ class SurrealConfigDict(ConfigDict):
13
+ """
14
+ SurrealConfigDict is a configuration dictionary for SurrealDB models.
15
+
16
+ Attributes:
17
+ primary_key (str | None): The primary key field name for the model.
18
+ """
19
+
20
+ primary_key: str | None
21
+ " The primary key field name for the model. "
22
+
23
+
14
24
  class BaseSurrealModel(BaseModel):
15
25
  """
16
26
  Base class for models interacting with SurrealDB.
17
27
  """
18
28
 
19
- __pydantic_model_cache__: Type[BaseModel] | None = None
20
-
21
- def __init__(self, **data: Any):
22
- model_cls = self._init_model()
23
- instance = model_cls(**data)
24
- object.__setattr__(self, "_data", instance.model_dump())
25
- object.__setattr__(self, "_table_name", self.__class__.__name__)
29
+ @classmethod
30
+ def get_table_name(cls) -> str:
31
+ """
32
+ Get the table name for the model.
33
+ """
34
+ return cls.__name__
26
35
 
27
- def __getattr__(self, item: str) -> Any:
36
+ @classmethod
37
+ def get_index_primary_key(cls) -> str | None:
28
38
  """
29
- If the item is a field in _data, return it,
30
- otherwise, let the normal mechanism raise AttributeError.
39
+ Get the primary key field name for the model.
31
40
  """
32
- _data = object.__getattribute__(self, "_data")
33
- if item in _data:
34
- return _data[item]
35
- raise AttributeError(f"'{type(self).__name__}' object has no attribute '{item}'.")
41
+ if hasattr(cls, "model_config"): # pragma: no cover
42
+ primary_key = cls.model_config.get("primary_key", None)
43
+ if isinstance(primary_key, str):
44
+ return primary_key
36
45
 
37
- def __str__(self) -> str:
38
- return f"{self._data}"
46
+ return None
39
47
 
40
- def __setattr__(self, key: str, value: Any) -> None:
48
+ def get_id(self) -> None | str | RecordID:
41
49
  """
42
- If we want to allow updates, reinstantiate a Pydantic model
43
- with the new value.
50
+ Get the ID of the model instance.
44
51
  """
45
- if key in ("_data",): # and other internal attributes
46
- object.__setattr__(self, key, value)
47
- else:
48
- # Update the dict, validate via Pydantic, etc.
49
- current_data = dict(object.__getattribute__(self, "_data"))
50
- current_data[key] = value
51
- instance = self._init_model()(**current_data)
52
- object.__setattr__(self, "_data", instance.model_dump())
52
+ if hasattr(self, "id"):
53
+ id_value = getattr(self, "id")
54
+ return str(id_value) if id_value is not None else None
55
+
56
+ if hasattr(self, "model_config"):
57
+ primary_key = self.model_config.get("primary_key", None)
58
+ if isinstance(primary_key, str) and hasattr(self, primary_key):
59
+ primary_key_value = getattr(self, primary_key)
60
+ return str(primary_key_value) if primary_key_value is not None else None
61
+
62
+ return None # pragma: no cover
53
63
 
54
64
  @classmethod
55
- def from_db(cls, record: dict | list) -> Any:
65
+ def from_db(cls, record: dict | list) -> Self | list[Self]:
56
66
  """
57
67
  Create an instance from a SurrealDB record.
58
68
  """
59
69
  if isinstance(record, list):
60
- return [cls.from_db(rs) for rs in record]
61
-
62
- record = cls.__set_data(record)
70
+ return [cls.from_db(rs) for rs in record] # type: ignore
63
71
 
64
72
  return cls(**record)
65
73
 
66
- def to_db_dict(self) -> dict[str, Any]:
67
- """
68
- Return a dictionary ready to be inserted into the database.
69
- """
70
- data_set = {key: value for key, value in self._data.items() if not key.startswith("_") and key != "id"}
71
- return data_set
72
-
73
- def show_config(self) -> ConfigDict:
74
- # Accès depuis une méthode d'instance
75
- return type(self).model_config
76
-
77
- def get_id(self) -> str | RecordID | None:
78
- if "id" in self._data:
79
- return self._data["id"]
80
-
81
- config = self.show_config()
82
- pk_field = config.get("primary_key", "id")
83
- return self._data.get(pk_field, None)
84
-
85
- @staticmethod
86
- def __set_data(data: Any) -> dict:
74
+ @model_validator(mode="before")
75
+ @classmethod
76
+ def set_data(cls, data: Any) -> Any:
87
77
  """
88
- Set the model instance data.
78
+ Set the ID of the model instance.
89
79
  """
90
80
  if isinstance(data, dict): # pragma: no cover
91
- if "id" in data and isinstance(data["id"], RecordID): # pragma: no cover
81
+ if "id" in data and isinstance(data["id"], RecordID):
92
82
  data["id"] = str(data["id"]).split(":")[1]
93
83
  return data
94
84
 
95
- raise TypeError("Data must be a dictionary.") # pragma: no cover
96
-
97
85
  async def refresh(self) -> None:
98
86
  """
99
87
  Refresh the model instance from the database.
100
88
  """
89
+ if not self.get_id():
90
+ raise SurrealDbError("Can't refresh data, not recorded yet.") # pragma: no cover
91
+
101
92
  client = await SurrealDBConnectionManager.get_client()
102
- record = None
93
+ record = await client.select(f"{self.get_table_name()}:{self.get_id()}")
103
94
 
104
- id = self.get_id()
105
- record = await client.select(f"{self._table_name}:{id}")
95
+ if record is None:
96
+ raise SurrealDbError("Can't refresh data, no record found.") # pragma: no cover
106
97
 
107
- self._data = self.__set_data(record)
98
+ self.from_db(record)
99
+ return None
108
100
 
109
101
  async def save(self) -> Self:
110
102
  """
111
103
  Save the model instance to the database.
112
104
  """
113
105
  client = await SurrealDBConnectionManager.get_client()
114
-
115
- data = self.to_db_dict()
106
+ data = self.model_dump(exclude={"id"})
116
107
  id = self.get_id()
117
- if id:
118
- thing = f"{self._table_name}:{id}"
108
+ table = self.get_table_name()
109
+
110
+ if id is not None:
111
+ thing = f"{table}:{id}"
119
112
  await client.create(thing, data)
120
113
  return self
114
+
121
115
  # Auto-generate the ID
122
- record = await client.create(self._table_name, data) # pragma: no cover
123
- if isinstance(record, dict): # pragma: no cover
124
- self._data = self.__set_data(record)
116
+ record = await client.create(table, data) # pragma: no cover
125
117
 
126
- return self
118
+ if isinstance(record, list):
119
+ raise SurrealDbError("Can't save data, multiple records returned.") # pragma: no cover
120
+
121
+ if record is None:
122
+ raise SurrealDbError("Can't save data, no record returned.") # pragma: no cover
123
+
124
+ obj = self.from_db(record)
125
+ if isinstance(obj, type(self)):
126
+ self = obj
127
+ return self
128
+
129
+ raise SurrealDbError("Can't save data, no record returned.") # pragma: no cover
127
130
 
128
131
  async def update(self) -> Any:
129
132
  """
@@ -131,12 +134,12 @@ class BaseSurrealModel(BaseModel):
131
134
  """
132
135
  client = await SurrealDBConnectionManager.get_client()
133
136
 
134
- data = self.to_db_dict()
137
+ data = self.model_dump(exclude={"id"})
135
138
  id = self.get_id()
136
- if id:
137
- thing = f"{self._table_name}:{id}"
138
- return await client.update(thing, data)
139
-
139
+ if id is not None:
140
+ thing = f"{self.__class__.__name__}:{id}"
141
+ test = await client.update(thing, data)
142
+ return test
140
143
  raise SurrealDbError("Can't update data, no id found.")
141
144
 
142
145
  async def merge(self, **data: Any) -> Any:
@@ -149,7 +152,7 @@ class BaseSurrealModel(BaseModel):
149
152
 
150
153
  id = self.get_id()
151
154
  if id:
152
- thing = f"{self._table_name}:{id}"
155
+ thing = f"{self.get_table_name()}:{id}"
153
156
 
154
157
  await client.merge(thing, data_set)
155
158
  await self.refresh()
@@ -166,7 +169,7 @@ class BaseSurrealModel(BaseModel):
166
169
 
167
170
  id = self.get_id()
168
171
 
169
- thing = f"{self._table_name}:{id}"
172
+ thing = f"{self.get_table_name()}:{id}"
170
173
 
171
174
  deleted = await client.delete(thing)
172
175
 
@@ -174,50 +177,20 @@ class BaseSurrealModel(BaseModel):
174
177
  raise SurrealDbError(f"Can't delete Record id -> '{id}' not found!")
175
178
 
176
179
  logger.info(f"Record deleted -> {deleted}.")
177
- self._data = {}
178
180
  del self
179
181
 
180
- @classmethod
181
- def _init_model(cls) -> Any:
182
- """
183
- Generate a real Pydantic model only once (per subclass)
184
- from the fields annotated in the class inheriting from BaseSurrealModel.
185
- """
186
- if cls.__pydantic_model_cache__ is not None:
187
- return cls.__pydantic_model_cache__
188
-
189
- # Retrieve the annotations declared in the class (e.g., ModelTest)
190
- hints: dict[str, Any] = {}
191
- config_dict = None
192
- for base in reversed(cls.__mro__): # To capture all annotations
193
- hints.update(getattr(base, "__annotations__", {}))
194
- # Optionally, check if the class has 'model_config' to inject it
195
- if hasattr(base, "model_config"):
196
- config_dict = getattr(base, "model_config")
197
-
198
- # Create the Pydantic model (dynamically)
199
- fields = {}
200
- for field_name, field_type in hints.items():
201
- # Read the object already defined in the class (if Field(...))
202
- default_val = getattr(cls, field_name, ...)
203
- fields[field_name] = (field_type, default_val)
204
-
205
- # Create model
206
- if config_dict:
207
- pyd_model = create_model( # type: ignore
208
- f"{cls.__name__}PydModel",
209
- __config__=config_dict,
210
- **fields,
211
- )
212
- else:
213
- pyd_model = create_model( # type: ignore
214
- f"{cls.__name__}PydModel",
215
- __base__=BaseModel,
216
- **fields,
182
+ @model_validator(mode="after")
183
+ def check_config(self) -> Self:
184
+ """
185
+ Check the model configuration.
186
+ """
187
+
188
+ if not self.get_index_primary_key() and not hasattr(self, "id"):
189
+ raise SurrealDbError( # pragma: no cover
190
+ "Can't create model, the model need either 'id' field or primirary_key in 'model_config'."
217
191
  )
218
192
 
219
- cls.__pydantic_model_cache__ = pyd_model
220
- return pyd_model
193
+ return self
221
194
 
222
195
  @classmethod
223
196
  def objects(cls) -> Any:
surreal_orm/py.typed ADDED
File without changes
surreal_orm/query_set.py CHANGED
@@ -301,7 +301,6 @@ class QuerySet:
301
301
  results = await self._execute_query(query)
302
302
  try:
303
303
  data = cast(dict, results[0])
304
-
305
304
  return self.model.from_db(data["result"])
306
305
  except ValidationError as e:
307
306
  logger.info(f"Pydantic invalid format for the class, returning dict value: {e}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: surrealdb-orm
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: SurrealDB ORM as 'DJango style' for Python with async support. Works with pydantic validation.
5
5
  Project-URL: Homepage, https://github.com/EulogySnowfall/SurrealDB-ORM
6
6
  Project-URL: Documentation, https://github.com/EulogySnowfall/SurrealDB-ORM
@@ -70,7 +70,7 @@ Description-Content-Type: text/markdown
70
70
 
71
71
  ## ✅ Version
72
72
 
73
- Alpha 0.1.3
73
+ Alpha 0.1.4
74
74
 
75
75
  ---
76
76
 
@@ -0,0 +1,12 @@
1
+ surreal_orm/__init__.py,sha256=p2dnNi1Ar0FNrLlC7oHtYiUrrdYwQKt--72_-omCElk,306
2
+ surreal_orm/connection_manager.py,sha256=kRAK6qhkKRVbhTjWZV6LaOGiH_jClwQ3zzmjca1A5Ro,8561
3
+ surreal_orm/constants.py,sha256=CLavEca1M6cLJLqVl4l4KoE-cBrgVQNsuGxW9zGJBmg,429
4
+ surreal_orm/enum.py,sha256=kR-vzkHqnqy9YaYOvWTwAHdl2-WCzPcSEch-YTyJv1Y,158
5
+ surreal_orm/model_base.py,sha256=zz6y8TQavk0XvLhunzQflJpPG85FizeCR-mAy-LHlK8,6082
6
+ surreal_orm/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ surreal_orm/query_set.py,sha256=VD8F2vKv5_uIXRCV_0HCx6lnarF-Qx3x8MYJq2LH78U,18155
8
+ surreal_orm/utils.py,sha256=mni_dTtb4VGTdge8eWSZpBw5xoWci2m-XThKFHYPKTo,171
9
+ surrealdb_orm-0.1.4.dist-info/METADATA,sha256=AFDw-vOKASG0mGa73Zo9CcaQKe-E-ocia3tOUJdcELY,5980
10
+ surrealdb_orm-0.1.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
11
+ surrealdb_orm-0.1.4.dist-info/licenses/LICENSE,sha256=TO3Ub0nPPx5NxwjsDuBAu3RBdBLmdDybqC5dem4oGts,1074
12
+ surrealdb_orm-0.1.4.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- surreal_orm/__init__.py,sha256=RNoDt0Gt7_jTjzxzXGPXKUqe7EnCdt31b35tnA-JcOM,243
2
- surreal_orm/connection_manager.py,sha256=kRAK6qhkKRVbhTjWZV6LaOGiH_jClwQ3zzmjca1A5Ro,8561
3
- surreal_orm/constants.py,sha256=CLavEca1M6cLJLqVl4l4KoE-cBrgVQNsuGxW9zGJBmg,429
4
- surreal_orm/enum.py,sha256=kR-vzkHqnqy9YaYOvWTwAHdl2-WCzPcSEch-YTyJv1Y,158
5
- surreal_orm/model_base.py,sha256=U9ZJeOS75uJXMvhKTNy8I2QDXMxoF5eVUkQe-vesggk,7358
6
- surreal_orm/query_set.py,sha256=ng-jTFWiUpxPJH-KC7SNxZ2PwmciCnkOifOWa7A2KEs,18156
7
- surreal_orm/utils.py,sha256=mni_dTtb4VGTdge8eWSZpBw5xoWci2m-XThKFHYPKTo,171
8
- surrealdb_orm-0.1.3.dist-info/METADATA,sha256=T9tXMFp3th-r4FMnFQt2s8z4Pz5zCs0_KoE33k64-gU,5980
9
- surrealdb_orm-0.1.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
- surrealdb_orm-0.1.3.dist-info/licenses/LICENSE,sha256=TO3Ub0nPPx5NxwjsDuBAu3RBdBLmdDybqC5dem4oGts,1074
11
- surrealdb_orm-0.1.3.dist-info/RECORD,,