surrealdb-orm 0.1.2__tar.gz → 0.1.4__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.

Potentially problematic release.


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

@@ -32,7 +32,6 @@ junit.xml
32
32
 
33
33
  # Jupyter Notebook
34
34
  .ipynb_checkpoints
35
- *.ipynb
36
35
 
37
36
  # Environment directories
38
37
  .env
@@ -1,5 +1,5 @@
1
1
  .DEFAULT_GOAL := all
2
- sources = src tests
2
+ sources = src
3
3
 
4
4
  .PHONY: .uv # Check that uv is installed
5
5
  .uv:
@@ -20,6 +20,10 @@ lint:
20
20
  uv run ruff format --check
21
21
  uv run ruff check
22
22
 
23
+ .PHONY: mypy
24
+ mypy:
25
+ uv run python -m mypy $(sources)
26
+
23
27
  .PHONY: typecheck
24
28
  typecheck:
25
29
  uv run pyright
@@ -41,4 +45,4 @@ html: test-all-python
41
45
  uv run coverage html -d htmlcov
42
46
 
43
47
  .PHONY: all
44
- all: format lint typecheck test-all-python
48
+ all: format mypy lint typecheck test-all-python
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: surrealdb-orm
3
- Version: 0.1.2
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.2
73
+ Alpha 0.1.4
74
74
 
75
75
  ---
76
76
 
@@ -25,7 +25,7 @@
25
25
 
26
26
  ## ✅ Version
27
27
 
28
- Alpha 0.1.2
28
+ Alpha 0.1.4
29
29
 
30
30
  ---
31
31
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "surrealdb-orm"
3
- version = "0.1.2"
3
+ version = "0.1.4"
4
4
  description = "SurrealDB ORM as 'DJango style' for Python with async support. Works with pydantic validation."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -32,7 +32,7 @@ Repository = "https://github.com/EulogySnowfall/SurrealDB-ORM.git"
32
32
  Issues = "https://github.com/EulogySnowfall/SurrealDB-ORM/issues"
33
33
 
34
34
  [tool.setuptools]
35
- packages = ["src.surrealdb_orm"]
35
+ packages = ["src/surreal_orm"]
36
36
 
37
37
 
38
38
  [build-system]
@@ -42,6 +42,7 @@ build-backend = "hatchling.build"
42
42
  [tool.ruff]
43
43
  line-length = 128
44
44
  target-version = "py311"
45
+ exclude = ["examples"]
45
46
 
46
47
  [tool.mypy]
47
48
  python_version = "3.11"
@@ -87,7 +88,7 @@ lint = [
87
88
 
88
89
 
89
90
  [tool.hatch.build.targets.wheel]
90
- packages = ["src.surreal_orm"]
91
+ packages = ["src/surreal_orm"]
91
92
 
92
93
  [tool.hatch.build.targets.sdist]
93
- include = ["/README.md", "/Makefile", "/src", "/tests"]
94
+ include = ["README.md", "Makefile", "/src"]
@@ -0,0 +1,12 @@
1
+ from .model_base import BaseSurrealModel, SurrealConfigDict
2
+ from .connection_manager import SurrealDBConnectionManager
3
+ from .query_set import QuerySet
4
+ from .enum import OrderBy
5
+
6
+ __all__ = [
7
+ "SurrealDBConnectionManager",
8
+ "BaseSurrealModel",
9
+ "QuerySet",
10
+ "OrderBy",
11
+ "SurrealConfigDict",
12
+ ]
@@ -0,0 +1,202 @@
1
+ from typing import Any, Self
2
+ from pydantic import BaseModel, ConfigDict, model_validator
3
+ from .connection_manager import SurrealDBConnectionManager
4
+ from surrealdb import RecordID, SurrealDbError
5
+
6
+ import logging
7
+
8
+
9
+ logger = logging.getLogger(__name__)
10
+
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
+
24
+ class BaseSurrealModel(BaseModel):
25
+ """
26
+ Base class for models interacting with SurrealDB.
27
+ """
28
+
29
+ @classmethod
30
+ def get_table_name(cls) -> str:
31
+ """
32
+ Get the table name for the model.
33
+ """
34
+ return cls.__name__
35
+
36
+ @classmethod
37
+ def get_index_primary_key(cls) -> str | None:
38
+ """
39
+ Get the primary key field name for the model.
40
+ """
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
45
+
46
+ return None
47
+
48
+ def get_id(self) -> None | str | RecordID:
49
+ """
50
+ Get the ID of the model instance.
51
+ """
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
63
+
64
+ @classmethod
65
+ def from_db(cls, record: dict | list) -> Self | list[Self]:
66
+ """
67
+ Create an instance from a SurrealDB record.
68
+ """
69
+ if isinstance(record, list):
70
+ return [cls.from_db(rs) for rs in record] # type: ignore
71
+
72
+ return cls(**record)
73
+
74
+ @model_validator(mode="before")
75
+ @classmethod
76
+ def set_data(cls, data: Any) -> Any:
77
+ """
78
+ Set the ID of the model instance.
79
+ """
80
+ if isinstance(data, dict): # pragma: no cover
81
+ if "id" in data and isinstance(data["id"], RecordID):
82
+ data["id"] = str(data["id"]).split(":")[1]
83
+ return data
84
+
85
+ async def refresh(self) -> None:
86
+ """
87
+ Refresh the model instance from the database.
88
+ """
89
+ if not self.get_id():
90
+ raise SurrealDbError("Can't refresh data, not recorded yet.") # pragma: no cover
91
+
92
+ client = await SurrealDBConnectionManager.get_client()
93
+ record = await client.select(f"{self.get_table_name()}:{self.get_id()}")
94
+
95
+ if record is None:
96
+ raise SurrealDbError("Can't refresh data, no record found.") # pragma: no cover
97
+
98
+ self.from_db(record)
99
+ return None
100
+
101
+ async def save(self) -> Self:
102
+ """
103
+ Save the model instance to the database.
104
+ """
105
+ client = await SurrealDBConnectionManager.get_client()
106
+ data = self.model_dump(exclude={"id"})
107
+ id = self.get_id()
108
+ table = self.get_table_name()
109
+
110
+ if id is not None:
111
+ thing = f"{table}:{id}"
112
+ await client.create(thing, data)
113
+ return self
114
+
115
+ # Auto-generate the ID
116
+ record = await client.create(table, data) # pragma: no cover
117
+
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
130
+
131
+ async def update(self) -> Any:
132
+ """
133
+ Update the model instance to the database.
134
+ """
135
+ client = await SurrealDBConnectionManager.get_client()
136
+
137
+ data = self.model_dump(exclude={"id"})
138
+ id = self.get_id()
139
+ if id is not None:
140
+ thing = f"{self.__class__.__name__}:{id}"
141
+ test = await client.update(thing, data)
142
+ return test
143
+ raise SurrealDbError("Can't update data, no id found.")
144
+
145
+ async def merge(self, **data: Any) -> Any:
146
+ """
147
+ Update the model instance to the database.
148
+ """
149
+
150
+ client = await SurrealDBConnectionManager.get_client()
151
+ data_set = {key: value for key, value in data.items()}
152
+
153
+ id = self.get_id()
154
+ if id:
155
+ thing = f"{self.get_table_name()}:{id}"
156
+
157
+ await client.merge(thing, data_set)
158
+ await self.refresh()
159
+ return
160
+
161
+ raise SurrealDbError(f"No Id for the data to merge: {data}")
162
+
163
+ async def delete(self) -> None:
164
+ """
165
+ Delete the model instance from the database.
166
+ """
167
+
168
+ client = await SurrealDBConnectionManager.get_client()
169
+
170
+ id = self.get_id()
171
+
172
+ thing = f"{self.get_table_name()}:{id}"
173
+
174
+ deleted = await client.delete(thing)
175
+
176
+ if not deleted:
177
+ raise SurrealDbError(f"Can't delete Record id -> '{id}' not found!")
178
+
179
+ logger.info(f"Record deleted -> {deleted}.")
180
+ del self
181
+
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'."
191
+ )
192
+
193
+ return self
194
+
195
+ @classmethod
196
+ def objects(cls) -> Any:
197
+ """
198
+ Return a QuerySet for the model class.
199
+ """
200
+ from .query_set import QuerySet
201
+
202
+ return QuerySet(cls)
@@ -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 +0,0 @@
1
- from .model_base import BaseSurrealModel
2
- from .connection_manager import SurrealDBConnectionManager
3
- from .query_set import QuerySet
4
- from .enum import OrderBy
5
-
6
- __all__ = ["SurrealDBConnectionManager", "BaseSurrealModel", "QuerySet", "OrderBy"]
@@ -1,229 +0,0 @@
1
- from typing import Any, Type, Self
2
- from pydantic import BaseModel, create_model, ConfigDict
3
- from .connection_manager import SurrealDBConnectionManager
4
- from surrealdb import RecordID, SurrealDbError
5
-
6
- import warnings
7
- import logging
8
-
9
- warnings.filterwarnings("ignore", message="fields may not start with an underscore", category=RuntimeWarning)
10
-
11
- logger = logging.getLogger(__name__)
12
-
13
-
14
- class BaseSurrealModel(BaseModel):
15
- """
16
- Base class for models interacting with SurrealDB.
17
- """
18
-
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__)
26
-
27
- def __getattr__(self, item: str) -> Any:
28
- """
29
- If the item is a field in _data, return it,
30
- otherwise, let the normal mechanism raise AttributeError.
31
- """
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}'.")
36
-
37
- def __str__(self) -> str:
38
- return f"{self._data}"
39
-
40
- def __setattr__(self, key: str, value: Any) -> None:
41
- """
42
- If we want to allow updates, reinstantiate a Pydantic model
43
- with the new value.
44
- """
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())
53
-
54
- @classmethod
55
- def from_db(cls, record: dict | list) -> Any:
56
- """
57
- Create an instance from a SurrealDB record.
58
- """
59
- if isinstance(record, list):
60
- return [cls.from_db(rs) for rs in record]
61
-
62
- record = cls.__set_data(record)
63
-
64
- return cls(**record)
65
-
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:
87
- """
88
- Set the model instance data.
89
- """
90
- if isinstance(data, dict): # pragma: no cover
91
- if "id" in data and isinstance(data["id"], RecordID): # pragma: no cover
92
- data["id"] = str(data["id"]).split(":")[1]
93
- return data
94
-
95
- raise TypeError("Data must be a dictionary.") # pragma: no cover
96
-
97
- async def refresh(self) -> None:
98
- """
99
- Refresh the model instance from the database.
100
- """
101
- client = await SurrealDBConnectionManager.get_client()
102
- record = None
103
-
104
- id = self.get_id()
105
- record = await client.select(f"{self._table_name}:{id}")
106
-
107
- self._data = self.__set_data(record)
108
-
109
- async def save(self) -> Self:
110
- """
111
- Save the model instance to the database.
112
- """
113
- client = await SurrealDBConnectionManager.get_client()
114
-
115
- data = self.to_db_dict()
116
- id = self.get_id()
117
- if id:
118
- thing = f"{self._table_name}:{id}"
119
- await client.create(thing, data)
120
- return self
121
- # 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)
125
-
126
- return self
127
-
128
- async def update(self) -> Any:
129
- """
130
- Update the model instance to the database.
131
- """
132
- client = await SurrealDBConnectionManager.get_client()
133
-
134
- data = self.to_db_dict()
135
- id = self.get_id()
136
- if id:
137
- thing = f"{self._table_name}:{id}"
138
- return await client.update(thing, data)
139
-
140
- raise SurrealDbError("Can't update data, no id found.")
141
-
142
- async def merge(self, **data: Any) -> Any:
143
- """
144
- Update the model instance to the database.
145
- """
146
-
147
- client = await SurrealDBConnectionManager.get_client()
148
- data_set = {key: value for key, value in data.items()}
149
-
150
- id = self.get_id()
151
- if id:
152
- thing = f"{self._table_name}:{id}"
153
-
154
- await client.merge(thing, data_set)
155
- await self.refresh()
156
- return
157
-
158
- raise SurrealDbError(f"No Id for the data to merge: {data}")
159
-
160
- async def delete(self) -> None:
161
- """
162
- Delete the model instance from the database.
163
- """
164
-
165
- client = await SurrealDBConnectionManager.get_client()
166
-
167
- id = self.get_id()
168
-
169
- thing = f"{self._table_name}:{id}"
170
-
171
- deleted = await client.delete(thing)
172
-
173
- if not deleted:
174
- raise SurrealDbError(f"Can't delete Record id -> '{id}' not found!")
175
-
176
- logger.info(f"Record deleted -> {deleted}.")
177
- self._data = {}
178
- del self
179
-
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,
217
- )
218
-
219
- cls.__pydantic_model_cache__ = pyd_model
220
- return pyd_model
221
-
222
- @classmethod
223
- def objects(cls) -> Any:
224
- """
225
- Return a QuerySet for the model class.
226
- """
227
- from .query_set import QuerySet
228
-
229
- return QuerySet(cls)
@@ -1,250 +0,0 @@
1
- import pytest
2
- from pydantic import ConfigDict, Field
3
- from src import surreal_orm
4
- from surrealdb import RecordID
5
- from surrealdb.errors import SurrealDbError
6
-
7
-
8
- SURREALDB_URL = "http://localhost:8000"
9
- SURREALDB_USER = "root"
10
- SURREALDB_PASS = "root"
11
- SURREALDB_NAMESPACE = "ns"
12
- SURREALDB_DATABASE = "db"
13
-
14
-
15
- class ModelTest(surreal_orm.BaseSurrealModel):
16
- model_config = ConfigDict(extra="allow")
17
- id: str | RecordID | None = Field(default=None)
18
- name: str = Field(..., max_length=100)
19
- age: int = Field(..., ge=0, le=125)
20
-
21
-
22
- class ModelTestEmpty(surreal_orm.BaseSurrealModel):
23
- model_config = ConfigDict(extra="allow")
24
- id: str | RecordID | None = Field(default=None)
25
- name: str = Field(..., max_length=100)
26
- age: int = Field(..., ge=0, le=125)
27
-
28
-
29
- @pytest.fixture(scope="module", autouse=True)
30
- def setup_surrealdb() -> None:
31
- # Initialiser SurrealDB
32
- surreal_orm.SurrealDBConnectionManager.set_connection(
33
- SURREALDB_URL,
34
- SURREALDB_USER,
35
- SURREALDB_PASS,
36
- SURREALDB_NAMESPACE,
37
- SURREALDB_DATABASE,
38
- )
39
-
40
-
41
- @pytest.mark.filterwarnings("ignore:fields may not")
42
- async def test_save_model() -> None:
43
- model = ModelTest(id="1", name="Test", age=32)
44
- await model.save()
45
-
46
- # Vérification de l'insertion
47
- client = await surreal_orm.SurrealDBConnectionManager.get_client()
48
- result = await client.select("ModelTest")
49
- test_id = RecordID(table_name="ModelTest", identifier=1)
50
- assert len(result) == 1
51
-
52
- assert result[0] == {"id": test_id, "name": "Test", "age": 32}
53
-
54
-
55
- async def test_merge_model() -> None:
56
- item = await ModelTest.objects().get(1)
57
- assert item.name == "Test"
58
- assert item.age == 32
59
- await item.merge(age=23) # Also test whole refresh() method
60
- item.age = 23
61
- item.name = "Test"
62
- item.id = "1"
63
-
64
- item2 = await ModelTest.objects().filter(name="Test").get()
65
- assert item2.age == 23
66
- assert item2.name == "Test"
67
- assert item2.id == "1"
68
-
69
-
70
- async def test_update_model() -> None:
71
- item = await ModelTest.objects().get(1)
72
- assert item.name == "Test"
73
- assert item.age == 23
74
- item.age = 25
75
- await item.update()
76
- item2 = await ModelTest.objects().get(1)
77
- assert item2.age == 25
78
-
79
- item2 = await ModelTest.objects().filter(name="Test").get()
80
- assert item2.age == 25
81
- assert item2.name == "Test"
82
- assert item2.id == "1"
83
-
84
- item3 = ModelTest(id=None, name="TestNone", age=17)
85
-
86
- with pytest.raises(SurrealDbError) as exc1:
87
- await item3.update()
88
-
89
- assert str(exc1.value) == "Can't update data, no id found."
90
-
91
- with pytest.raises(SurrealDbError) as exc2:
92
- await item3.merge(age=19)
93
-
94
- assert str(exc2.value) == "No Id for the data to merge: {'age': 19}"
95
-
96
-
97
- async def test_first_model() -> None:
98
- model = await ModelTest.objects().filter(name="Test").first()
99
- if isinstance(model, ModelTest):
100
- assert model.name == "Test"
101
- assert model.age == 25
102
- assert model.id == "1"
103
- else:
104
- assert False
105
-
106
- with pytest.raises(SurrealDbError) as exc1:
107
- await ModelTest.objects().filter(name="NotExist").first()
108
-
109
- assert str(exc1.value) == "No result found."
110
-
111
-
112
- async def test_filter_model() -> None:
113
- item3 = ModelTest(id=None, name="Test2", age=17)
114
- await item3.save()
115
-
116
- models = await ModelTest.objects().filter(age__lt=30).exec() # Test from_db isinstance(record["id"], RecordID)
117
- assert len(models) == 2
118
- for model in models:
119
- assert model.age < 30
120
-
121
-
122
- async def test_delete_model() -> None:
123
- model = ModelTest(id="2", name="Test2", age=34)
124
- await model.save()
125
- client = await surreal_orm.SurrealDBConnectionManager.get_client()
126
- result = await client.select("ModelTest")
127
- assert len(result) == 3
128
-
129
- await model.delete()
130
- result = await client.select("ModelTest")
131
- assert len(result) == 2
132
-
133
- model2 = ModelTest(id="345", name="Test2", age=34)
134
-
135
- with pytest.raises(SurrealDbError) as exc1:
136
- await model2.delete() # Test delete() without saved()
137
-
138
- assert str(exc1.value) == "Can't delete Record id -> '345' not found!"
139
-
140
-
141
- async def test_query_model() -> None:
142
- # Utiliser test_model pour exécuter la requête
143
- results = await ModelTest.objects().filter(name="Test").exec()
144
- assert len(results) == 1
145
- assert results[0].name == "Test"
146
-
147
-
148
- async def test_multi_select() -> None:
149
- await ModelTest(id=None, name="Ian", age=23).save()
150
- await ModelTest(id=None, name="Yan", age=32).save()
151
- await ModelTest(id=None, name="Isa", age=32).save()
152
-
153
- result = await ModelTest.objects().all()
154
-
155
- assert len(result) == 5
156
-
157
- result2 = await ModelTest.objects().filter(name__in=["Ian", "Yan"]).exec()
158
-
159
- assert len(result2) == 2
160
- for item in result2:
161
- assert item.name in ["Yan", "Ian"]
162
-
163
-
164
- async def test_order_by() -> None:
165
- result1 = await ModelTest.objects().order_by("name").exec()
166
- assert len(result1) == 5
167
- assert result1[0].name == "Ian"
168
-
169
- result2 = await ModelTest.objects().order_by("name", surreal_orm.OrderBy.DESC).exec()
170
- assert len(result2) == 5
171
- assert result2[0].name == "Yan"
172
-
173
-
174
- async def test_offset() -> None:
175
- result = await ModelTest.objects().offset(2).exec()
176
- assert len(result) == 3
177
-
178
-
179
- async def test_limit() -> None:
180
- result = await ModelTest.objects().limit(2).exec()
181
- assert len(result) == 2
182
-
183
-
184
- async def test_select_field() -> None:
185
- result = await ModelTest.objects().select("name", "age").exec()
186
- assert len(result) == 5
187
- assert isinstance(result[0], dict)
188
-
189
-
190
- async def test_select_with_variable() -> None:
191
- result = await ModelTest.objects().filter(age__lte="$max_age").variables(max_age=25).exec()
192
- assert len(result) == 3
193
- for res in result:
194
- assert res.age <= 25
195
-
196
-
197
- async def test_query() -> None:
198
- result = await ModelTest.objects().query("SELECT * FROM ModelTest WHERE age > 25")
199
- assert len(result) == 2
200
- for res in result:
201
- assert res.age > 25
202
-
203
- result2 = await ModelTest.objects().query("SELECT * FROM ModelTest WHERE age > $age", {"age": 19})
204
- assert len(result2) == 4
205
-
206
- with pytest.raises(SurrealDbError) as exc:
207
- await ModelTest.objects().query("SELECT * FROM NoTable WHERE age > 34")
208
-
209
- assert str(exc.value) == "The query must include 'FROM ModelTest' to reference the correct table."
210
-
211
-
212
- async def test_error_on_get_multi() -> None:
213
- with pytest.raises(SurrealDbError) as exc1:
214
- await ModelTest.objects().get()
215
-
216
- assert str(exc1.value) == "More than one result found."
217
-
218
- with pytest.raises(SurrealDbError) as exc2:
219
- await ModelTestEmpty.objects().get()
220
-
221
- assert str(exc2.value) == "No result found."
222
-
223
-
224
- @pytest.mark.filterwarnings("ignore:fields may not")
225
- async def test_with_primary_key() -> None:
226
- class ModelTest2(surreal_orm.BaseSurrealModel):
227
- model_config = ConfigDict(extra="allow", primary_key="email") # type: ignore
228
- name: str = Field(..., max_length=100)
229
- age: int = Field(..., ge=0, le=125)
230
- email: str = Field(..., max_length=100)
231
-
232
- model = ModelTest2(name="Test", age=32, email="test@test.com")
233
- await model.save()
234
-
235
- fletch = await ModelTest2.objects().get("test@test.com")
236
- if isinstance(fletch, ModelTest2):
237
- assert fletch.name == "Test"
238
- assert fletch.age == 32
239
- assert fletch.email == "test@test.com"
240
- else:
241
- assert False
242
-
243
- deleted = await ModelTest2.objects().delete_table()
244
- assert deleted is True
245
-
246
-
247
- async def test_delete_table() -> None:
248
- # Suppression de la table via test_model
249
- result = await ModelTest.objects().delete_table()
250
- assert result is True
@@ -1,162 +0,0 @@
1
- import pytest
2
- from typing import AsyncGenerator, Any
3
- from src.surreal_orm import SurrealDBConnectionManager
4
- from surrealdb.errors import SurrealDbConnectionError
5
-
6
- SURREALDB_URL = "http://localhost:8000"
7
- SURREALDB_USER = "root"
8
- SURREALDB_PASS = "root"
9
- SURREALDB_NAMESPACE = "ns"
10
- SURREALDB_DATABASE = "db"
11
-
12
-
13
- @pytest.fixture
14
- async def setup_connection_manager() -> AsyncGenerator[Any, Any]:
15
- SurrealDBConnectionManager.set_connection(
16
- SURREALDB_URL,
17
- SURREALDB_USER,
18
- SURREALDB_PASS,
19
- SURREALDB_NAMESPACE,
20
- SURREALDB_DATABASE,
21
- )
22
- yield
23
- await SurrealDBConnectionManager.close_connection()
24
-
25
-
26
- def test_set_connection() -> None:
27
- assert SurrealDBConnectionManager.is_connection_set() is True
28
-
29
-
30
- async def test_get_client() -> None:
31
- client = await SurrealDBConnectionManager.get_client()
32
- assert client is not None
33
- assert SurrealDBConnectionManager.is_connected() is True
34
- await SurrealDBConnectionManager.close_connection()
35
- assert SurrealDBConnectionManager.is_connected() is False
36
- await SurrealDBConnectionManager.set_user("wrong_user")
37
-
38
- with pytest.raises(SurrealDbConnectionError) as exc1:
39
- await SurrealDBConnectionManager.get_client()
40
-
41
- await SurrealDBConnectionManager.unset_connection()
42
- with pytest.raises(ValueError) as exc2:
43
- await SurrealDBConnectionManager.get_client()
44
-
45
- assert str(exc1.value) == "Can't connect to the database."
46
- assert str(exc2.value) == "Connection not been set."
47
-
48
-
49
- async def test_close_connection() -> None:
50
- await SurrealDBConnectionManager.close_connection()
51
- assert SurrealDBConnectionManager.is_connected() is False
52
-
53
-
54
- async def test_get_connection_string(setup_connection_manager: AsyncGenerator[Any, Any]) -> None:
55
- connection_string = SurrealDBConnectionManager.get_connection_string()
56
- assert connection_string == SURREALDB_URL
57
-
58
-
59
- def test_get_connection_kwargs(setup_connection_manager: AsyncGenerator[Any, Any]) -> None:
60
- kwargs = SurrealDBConnectionManager.get_connection_kwargs()
61
- assert kwargs == {
62
- "url": SURREALDB_URL,
63
- "user": SURREALDB_USER,
64
- "namespace": SURREALDB_NAMESPACE,
65
- "database": SURREALDB_DATABASE,
66
- }
67
-
68
-
69
- def test_is_connection_set(setup_connection_manager: AsyncGenerator[Any, Any]) -> None:
70
- kwargs = SurrealDBConnectionManager.is_connection_set()
71
- assert kwargs is True
72
-
73
-
74
- async def test_set_url(setup_connection_manager: AsyncGenerator[Any, Any]) -> None:
75
- new_url = "http://localhost:8001"
76
- assert await SurrealDBConnectionManager.set_url(new_url) is True
77
- assert SurrealDBConnectionManager.get_url() == new_url
78
- assert await SurrealDBConnectionManager.set_url(new_url, True) is False # Cover validate_connection to False
79
-
80
- await SurrealDBConnectionManager.unset_connection()
81
- with pytest.raises(ValueError) as exc:
82
- await SurrealDBConnectionManager.set_url(new_url)
83
-
84
- assert str(exc.value) == "You can't change the URL when the others setting are not already set."
85
-
86
-
87
- async def test_set_user(setup_connection_manager: AsyncGenerator[Any, Any]) -> None:
88
- new_user = "admin"
89
- assert await SurrealDBConnectionManager.set_user(new_user) is True
90
- assert SurrealDBConnectionManager.get_user() == new_user
91
- assert await SurrealDBConnectionManager.set_user(new_user, True) is False # Cover validate_connection to False
92
- assert SurrealDBConnectionManager.get_user() is None
93
- await SurrealDBConnectionManager.unset_connection()
94
- with pytest.raises(ValueError) as exc:
95
- await SurrealDBConnectionManager.set_user(new_user)
96
-
97
- assert str(exc.value) == "You can't change the User when the others setting are not already set."
98
-
99
-
100
- async def test_set_password(setup_connection_manager: AsyncGenerator[Any, Any]) -> None:
101
- new_password = "new_pass"
102
- assert await SurrealDBConnectionManager.set_password(new_password) is True
103
- assert SurrealDBConnectionManager.is_password_set()
104
- assert await SurrealDBConnectionManager.set_password(new_password, True) is False # Cover validate_connection to False
105
- assert SurrealDBConnectionManager.is_password_set() is False
106
- assert SurrealDBConnectionManager.is_connection_set() is False
107
-
108
- await SurrealDBConnectionManager.unset_connection()
109
- with pytest.raises(ValueError) as exc:
110
- await SurrealDBConnectionManager.set_password(new_password)
111
-
112
- assert str(exc.value) == "You can't change the password when the others setting are not already set."
113
-
114
-
115
- async def test_set_namespace(setup_connection_manager: AsyncGenerator[Any, Any]) -> None:
116
- new_namespace = "new_ns"
117
- assert await SurrealDBConnectionManager.set_namespace(new_namespace) is True
118
- assert SurrealDBConnectionManager.get_namespace() == new_namespace
119
- assert await SurrealDBConnectionManager.set_namespace(new_namespace, True) is True
120
- await SurrealDBConnectionManager.set_password("wrong_pass")
121
- assert await SurrealDBConnectionManager.set_namespace(new_namespace, True) is False # Cover validate_connection to False
122
-
123
- await SurrealDBConnectionManager.unset_connection()
124
- with pytest.raises(ValueError) as exc:
125
- await SurrealDBConnectionManager.set_namespace(new_namespace)
126
-
127
- assert str(exc.value) == "You can't change the namespace when the others setting are not already set."
128
-
129
-
130
- async def test_set_database(setup_connection_manager: AsyncGenerator[Any, Any]) -> None:
131
- new_database = "new_db"
132
- assert await SurrealDBConnectionManager.set_database(new_database) is True
133
- assert SurrealDBConnectionManager.get_database() == new_database
134
- assert await SurrealDBConnectionManager.set_database(new_database, True) is True
135
- await SurrealDBConnectionManager.set_password("wrong_pass")
136
- assert await SurrealDBConnectionManager.set_database(new_database, True) is False # Cover validate_connection to False
137
-
138
- await SurrealDBConnectionManager.unset_connection()
139
- with pytest.raises(ValueError) as exc:
140
- await SurrealDBConnectionManager.set_database(new_database)
141
-
142
- assert str(exc.value) == "You can't change the database when the others setting are not already set."
143
-
144
-
145
- async def test_unset() -> None:
146
- await SurrealDBConnectionManager.unset_connection()
147
- assert SurrealDBConnectionManager.is_connected() is False
148
- assert SurrealDBConnectionManager.is_connection_set() is False
149
-
150
-
151
- async def test_reconnect(setup_connection_manager: AsyncGenerator[Any, Any]) -> None:
152
- await SurrealDBConnectionManager.close_connection()
153
- assert SurrealDBConnectionManager.is_connected() is False
154
- await SurrealDBConnectionManager.reconnect()
155
- assert SurrealDBConnectionManager.is_connected() is True
156
-
157
-
158
- async def test_context_manager(setup_connection_manager: AsyncGenerator[Any, Any]) -> None:
159
- async with SurrealDBConnectionManager():
160
- assert SurrealDBConnectionManager.is_connected() is True
161
-
162
- assert SurrealDBConnectionManager.is_connected() is False
@@ -1,104 +0,0 @@
1
- import pytest
2
- from pydantic import ConfigDict, Field
3
- from src import surreal_orm
4
-
5
-
6
- @pytest.fixture(scope="module", autouse=True)
7
- def setup_model() -> surreal_orm.BaseSurrealModel:
8
- class TestModel(surreal_orm.BaseSurrealModel):
9
- model_config = ConfigDict(extra="allow")
10
- id: str = Field(...)
11
- name: str = Field(..., max_length=100)
12
- age: int = Field(..., ge=0)
13
-
14
- return TestModel(id="1", name="Test", age=45)
15
-
16
-
17
- @pytest.mark.filterwarnings("ignore:fields may not")
18
- def test_model_get_query_set(setup_model: surreal_orm.BaseSurrealModel) -> None:
19
- query = setup_model.objects()
20
- assert isinstance(query, surreal_orm.QuerySet)
21
-
22
-
23
- def test_model_get_id(setup_model: surreal_orm.BaseSurrealModel) -> None:
24
- assert setup_model.get_id() == "1" # cover _data.get("id") is True
25
-
26
-
27
- def test_model_to_db_dict(setup_model: surreal_orm.BaseSurrealModel) -> None:
28
- assert setup_model.to_db_dict() == {"name": "Test", "age": 45}
29
-
30
-
31
- def test_queryset_select() -> None:
32
- qs = surreal_orm.BaseSurrealModel.objects().select("id", "name")
33
- assert qs.select_item == ["id", "name"]
34
-
35
-
36
- def test_queryset_filter() -> None:
37
- qs = surreal_orm.BaseSurrealModel.objects().filter(name="Test", age__gt=18)
38
- assert qs._filters == [("name", "exact", "Test"), ("age", "gt", 18)]
39
- qs = surreal_orm.BaseSurrealModel.objects().filter(name__in=["Test", "Test2"], age__gte=18)
40
- qs = surreal_orm.BaseSurrealModel.objects().filter(age__lte=45)
41
- assert qs._filters == [("age", "lte", 45)]
42
- qs = surreal_orm.BaseSurrealModel.objects().filter(age__lt=45)
43
- assert qs._filters == [("age", "lt", 45)]
44
-
45
-
46
- def test_queryset_variables(setup_model: surreal_orm.BaseSurrealModel) -> None:
47
- qs = setup_model.objects().variables(name="Test")
48
- assert qs._variables == {"name": "Test"}
49
-
50
-
51
- def test_queryset_limit(setup_model: surreal_orm.BaseSurrealModel) -> None:
52
- qs = setup_model.objects().limit(100)
53
- assert qs._limit == 100
54
-
55
-
56
- def test_queryset_offset(setup_model: surreal_orm.BaseSurrealModel) -> None:
57
- qs = setup_model.objects().offset(100)
58
- assert qs._offset == 100
59
-
60
-
61
- def test_queryset_order_by(setup_model: surreal_orm.BaseSurrealModel) -> None:
62
- qs = setup_model.objects().order_by("name")
63
- assert qs._order_by == "name ASC"
64
-
65
-
66
- def test_getattr(setup_model: surreal_orm.BaseSurrealModel) -> None:
67
- assert setup_model.name == "Test"
68
- assert setup_model.age == 45
69
- assert setup_model.id == "1"
70
-
71
- with pytest.raises(AttributeError) as exc:
72
- setup_model.no_attribut
73
-
74
- assert str(exc.value) == "'TestModel' object has no attribute 'no_attribut'."
75
-
76
-
77
- def test_str_dunnder(setup_model: surreal_orm.BaseSurrealModel) -> None:
78
- assert str(setup_model) == "{'id': '1', 'name': 'Test', 'age': 45}"
79
-
80
-
81
- @pytest.mark.filterwarnings("ignore:fields may not")
82
- def test_class_without_config(setup_model: surreal_orm.BaseSurrealModel) -> None:
83
- class TestModel2(surreal_orm.BaseSurrealModel):
84
- id: str = Field(...)
85
- name: str = Field(..., max_length=100)
86
- age: int = Field(..., ge=0)
87
-
88
- assert TestModel2(id="1", name="Test", age=45).to_db_dict() == {
89
- "name": "Test",
90
- "age": 45,
91
- }
92
-
93
-
94
- @pytest.mark.filterwarnings("ignore:fields may not")
95
- def test_class_with_key_specify(setup_model: surreal_orm.BaseSurrealModel) -> None:
96
- class TestModel3(surreal_orm.BaseSurrealModel):
97
- model_config = ConfigDict(extra="allow", primary_key="email") # type: ignore
98
- name: str = Field(..., max_length=100)
99
- age: int = Field(..., ge=0)
100
- email: str = Field(..., max_length=100, alias="id")
101
-
102
- model = TestModel3(name="Test", age=45, email="test@test.com") # type: ignore
103
-
104
- assert model.get_id() == "test@test.com" # type: ignore
File without changes