masonite-framework-orm 3.0.1__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.
- masonite_framework_orm-3.0.1.dist-info/METADATA +87 -0
- masonite_framework_orm-3.0.1.dist-info/RECORD +116 -0
- masonite_framework_orm-3.0.1.dist-info/WHEEL +5 -0
- masonite_framework_orm-3.0.1.dist-info/entry_points.txt +3 -0
- masonite_framework_orm-3.0.1.dist-info/licenses/LICENSE +21 -0
- masonite_framework_orm-3.0.1.dist-info/top_level.txt +1 -0
- masoniteorm/__init__.py +1 -0
- masoniteorm/collection/Collection.py +605 -0
- masoniteorm/collection/__init__.py +1 -0
- masoniteorm/commands/CanOverrideConfig.py +16 -0
- masoniteorm/commands/CanOverrideOptionsDefault.py +22 -0
- masoniteorm/commands/Command.py +6 -0
- masoniteorm/commands/Entry.py +43 -0
- masoniteorm/commands/MakeMigrationCommand.py +57 -0
- masoniteorm/commands/MakeModelCommand.py +78 -0
- masoniteorm/commands/MakeModelDocstringCommand.py +37 -0
- masoniteorm/commands/MakeObserverCommand.py +54 -0
- masoniteorm/commands/MakeSeedCommand.py +54 -0
- masoniteorm/commands/MigrateCommand.py +46 -0
- masoniteorm/commands/MigrateFreshCommand.py +41 -0
- masoniteorm/commands/MigrateRefreshCommand.py +41 -0
- masoniteorm/commands/MigrateResetCommand.py +25 -0
- masoniteorm/commands/MigrateRollbackCommand.py +26 -0
- masoniteorm/commands/MigrateStatusCommand.py +51 -0
- masoniteorm/commands/SeedRunCommand.py +35 -0
- masoniteorm/commands/ShellCommand.py +205 -0
- masoniteorm/commands/__init__.py +18 -0
- masoniteorm/commands/stubs/create_migration.stub +20 -0
- masoniteorm/commands/stubs/create_seed.stub +9 -0
- masoniteorm/commands/stubs/model.stub +9 -0
- masoniteorm/commands/stubs/observer.stub +101 -0
- masoniteorm/commands/stubs/table_migration.stub +19 -0
- masoniteorm/config.py +123 -0
- masoniteorm/connections/BaseConnection.py +101 -0
- masoniteorm/connections/ConnectionFactory.py +59 -0
- masoniteorm/connections/ConnectionResolver.py +132 -0
- masoniteorm/connections/MSSQLConnection.py +176 -0
- masoniteorm/connections/MySQLConnection.py +232 -0
- masoniteorm/connections/PostgresConnection.py +225 -0
- masoniteorm/connections/SQLiteConnection.py +179 -0
- masoniteorm/connections/__init__.py +6 -0
- masoniteorm/exceptions.py +38 -0
- masoniteorm/expressions/__init__.py +1 -0
- masoniteorm/expressions/expressions.py +288 -0
- masoniteorm/factories/Factory.py +112 -0
- masoniteorm/factories/__init__.py +1 -0
- masoniteorm/helpers/__init__.py +0 -0
- masoniteorm/helpers/misc.py +22 -0
- masoniteorm/migrations/Migration.py +330 -0
- masoniteorm/migrations/__init__.py +1 -0
- masoniteorm/models/MigrationModel.py +9 -0
- masoniteorm/models/Model.py +1209 -0
- masoniteorm/models/Model.pyi +1366 -0
- masoniteorm/models/Pivot.py +5 -0
- masoniteorm/models/__init__.py +1 -0
- masoniteorm/observers/ObservesEvents.py +27 -0
- masoniteorm/observers/__init__.py +1 -0
- masoniteorm/pagination/BasePaginator.py +10 -0
- masoniteorm/pagination/LengthAwarePaginator.py +34 -0
- masoniteorm/pagination/SimplePaginator.py +28 -0
- masoniteorm/pagination/__init__.py +2 -0
- masoniteorm/providers/ORMProvider.py +39 -0
- masoniteorm/providers/__init__.py +1 -0
- masoniteorm/query/EagerRelation.py +42 -0
- masoniteorm/query/QueryBuilder.py +2486 -0
- masoniteorm/query/__init__.py +1 -0
- masoniteorm/query/grammars/BaseGrammar.py +1027 -0
- masoniteorm/query/grammars/MSSQLGrammar.py +194 -0
- masoniteorm/query/grammars/MySQLGrammar.py +238 -0
- masoniteorm/query/grammars/PostgresGrammar.py +213 -0
- masoniteorm/query/grammars/SQLiteGrammar.py +228 -0
- masoniteorm/query/grammars/__init__.py +4 -0
- masoniteorm/query/processors/MSSQLPostProcessor.py +58 -0
- masoniteorm/query/processors/MySQLPostProcessor.py +48 -0
- masoniteorm/query/processors/PostgresPostProcessor.py +49 -0
- masoniteorm/query/processors/SQLitePostProcessor.py +49 -0
- masoniteorm/query/processors/__init__.py +4 -0
- masoniteorm/relationships/BaseRelationship.py +161 -0
- masoniteorm/relationships/BelongsTo.py +124 -0
- masoniteorm/relationships/BelongsToMany.py +604 -0
- masoniteorm/relationships/HasMany.py +66 -0
- masoniteorm/relationships/HasManyThrough.py +269 -0
- masoniteorm/relationships/HasOne.py +111 -0
- masoniteorm/relationships/HasOneThrough.py +275 -0
- masoniteorm/relationships/MorphMany.py +152 -0
- masoniteorm/relationships/MorphOne.py +156 -0
- masoniteorm/relationships/MorphTo.py +111 -0
- masoniteorm/relationships/MorphToMany.py +108 -0
- masoniteorm/relationships/__init__.py +10 -0
- masoniteorm/schema/Blueprint.py +1161 -0
- masoniteorm/schema/Column.py +144 -0
- masoniteorm/schema/ColumnDiff.py +0 -0
- masoniteorm/schema/Constraint.py +5 -0
- masoniteorm/schema/ForeignKeyConstraint.py +28 -0
- masoniteorm/schema/Index.py +5 -0
- masoniteorm/schema/Schema.py +359 -0
- masoniteorm/schema/Table.py +94 -0
- masoniteorm/schema/TableDiff.py +86 -0
- masoniteorm/schema/__init__.py +3 -0
- masoniteorm/schema/platforms/MSSQLPlatform.py +367 -0
- masoniteorm/schema/platforms/MySQLPlatform.py +513 -0
- masoniteorm/schema/platforms/Platform.py +97 -0
- masoniteorm/schema/platforms/PostgresPlatform.py +551 -0
- masoniteorm/schema/platforms/SQLitePlatform.py +481 -0
- masoniteorm/schema/platforms/__init__.py +4 -0
- masoniteorm/scopes/BaseScope.py +6 -0
- masoniteorm/scopes/SoftDeleteScope.py +56 -0
- masoniteorm/scopes/SoftDeletesMixin.py +13 -0
- masoniteorm/scopes/TimeStampsMixin.py +12 -0
- masoniteorm/scopes/TimeStampsScope.py +47 -0
- masoniteorm/scopes/UUIDPrimaryKeyMixin.py +8 -0
- masoniteorm/scopes/UUIDPrimaryKeyScope.py +51 -0
- masoniteorm/scopes/__init__.py +8 -0
- masoniteorm/scopes/scope.py +15 -0
- masoniteorm/seeds/Seeder.py +42 -0
- masoniteorm/seeds/__init__.py +1 -0
|
@@ -0,0 +1,1209 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
from datetime import date as datetimedate
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from datetime import time as datetimetime
|
|
7
|
+
from decimal import Decimal
|
|
8
|
+
from typing import Any, Dict
|
|
9
|
+
|
|
10
|
+
import pendulum
|
|
11
|
+
from inflection import tableize, underscore
|
|
12
|
+
|
|
13
|
+
from ..collection import Collection
|
|
14
|
+
from ..config import load_config
|
|
15
|
+
from ..exceptions import ModelNotFound
|
|
16
|
+
from ..observers import ObservesEvents
|
|
17
|
+
from ..query import QueryBuilder
|
|
18
|
+
from ..scopes import TimeStampsMixin
|
|
19
|
+
|
|
20
|
+
"""This is a magic class that will help using models like User.first() instead of having to instatiate a class like
|
|
21
|
+
User().first()
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ModelMeta(type):
|
|
26
|
+
def __getattr__(self, attribute, *args, **kwargs):
|
|
27
|
+
"""This method is called between a Model and accessing a property. This is a quick and easy
|
|
28
|
+
way to instantiate a class before the first method is called. This is to avoid needing
|
|
29
|
+
to do this:
|
|
30
|
+
|
|
31
|
+
User().where(..)
|
|
32
|
+
|
|
33
|
+
and instead, with this class inherited as a meta class, we can do this:
|
|
34
|
+
|
|
35
|
+
User.where(...)
|
|
36
|
+
|
|
37
|
+
This class (potentially magically) instantiates the class even though we really didn't instantiate it.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
attribute (string): The name of the attribute
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Model|mixed: An instantiated model's attribute
|
|
44
|
+
"""
|
|
45
|
+
instantiated = self()
|
|
46
|
+
return getattr(instantiated, attribute)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class BoolCast:
|
|
50
|
+
"""Casts a value to a boolean"""
|
|
51
|
+
|
|
52
|
+
def get(self, value):
|
|
53
|
+
"""
|
|
54
|
+
Cast the value to assign to the model attribute
|
|
55
|
+
"""
|
|
56
|
+
return bool(value)
|
|
57
|
+
|
|
58
|
+
def set(self, value):
|
|
59
|
+
"""
|
|
60
|
+
Cast the value for use in insert/update queries
|
|
61
|
+
"""
|
|
62
|
+
return bool(value)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class JsonCast:
|
|
66
|
+
"""Casts a value to JSON"""
|
|
67
|
+
|
|
68
|
+
def get(self, value):
|
|
69
|
+
"""
|
|
70
|
+
Cast the value to assign to the model attribute
|
|
71
|
+
"""
|
|
72
|
+
if isinstance(value, str):
|
|
73
|
+
try:
|
|
74
|
+
return json.loads(value)
|
|
75
|
+
except ValueError:
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
return value
|
|
79
|
+
|
|
80
|
+
def set(self, value):
|
|
81
|
+
"""
|
|
82
|
+
Cast the value for use in insert/update queries
|
|
83
|
+
"""
|
|
84
|
+
if isinstance(value, str):
|
|
85
|
+
# make sure the string is valid JSON
|
|
86
|
+
json.loads(value)
|
|
87
|
+
return value
|
|
88
|
+
|
|
89
|
+
return json.dumps(value, default=str)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class IntCast:
|
|
93
|
+
"""Casts a value to a int"""
|
|
94
|
+
|
|
95
|
+
def get(self, value):
|
|
96
|
+
"""
|
|
97
|
+
Cast the value to assign to the model attribute
|
|
98
|
+
"""
|
|
99
|
+
return int(value)
|
|
100
|
+
|
|
101
|
+
def set(self, value):
|
|
102
|
+
"""
|
|
103
|
+
Cast the value for use in insert/update queries
|
|
104
|
+
"""
|
|
105
|
+
return int(value)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class FloatCast:
|
|
109
|
+
"""Casts a value to a float"""
|
|
110
|
+
|
|
111
|
+
def get(self, value):
|
|
112
|
+
"""
|
|
113
|
+
Cast the value to assign to the model attribute
|
|
114
|
+
"""
|
|
115
|
+
return float(value)
|
|
116
|
+
|
|
117
|
+
def set(self, value):
|
|
118
|
+
"""
|
|
119
|
+
Cast the value for use in insert/update queries
|
|
120
|
+
"""
|
|
121
|
+
return float(value)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class DateCast:
|
|
125
|
+
"""Casts a value to a float"""
|
|
126
|
+
|
|
127
|
+
def get(self, value):
|
|
128
|
+
"""
|
|
129
|
+
Cast the value to assign to the model attribute
|
|
130
|
+
"""
|
|
131
|
+
return pendulum.parse(value).to_date_string()
|
|
132
|
+
|
|
133
|
+
def set(self, value):
|
|
134
|
+
"""
|
|
135
|
+
Cast the value for use in insert/update queries
|
|
136
|
+
"""
|
|
137
|
+
return pendulum.parse(value).to_date_string()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class DecimalCast:
|
|
141
|
+
"""Casts a value to Decimal for accuracy"""
|
|
142
|
+
|
|
143
|
+
def get(self, value):
|
|
144
|
+
"""
|
|
145
|
+
Cast the value to assign to the model attribute
|
|
146
|
+
"""
|
|
147
|
+
return Decimal(str(value))
|
|
148
|
+
|
|
149
|
+
def set(self, value):
|
|
150
|
+
"""
|
|
151
|
+
Cast the value for use in insert/update queries
|
|
152
|
+
"""
|
|
153
|
+
return str(value)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class Model(TimeStampsMixin, ObservesEvents, metaclass=ModelMeta):
|
|
157
|
+
"""The ORM Model class
|
|
158
|
+
|
|
159
|
+
Base Classes:
|
|
160
|
+
TimeStampsMixin (TimeStampsMixin): Adds scopes to add timestamps when something is inserted
|
|
161
|
+
metaclass (ModelMeta, optional): Helps instantiate a class when it hasn't been instantiated. Defaults to ModelMeta.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
__fillable__ = ["*"]
|
|
165
|
+
__guarded__ = []
|
|
166
|
+
__dry__ = False
|
|
167
|
+
__table__ = None
|
|
168
|
+
__connection__ = "default"
|
|
169
|
+
__resolved_connection__ = None
|
|
170
|
+
__selects__ = []
|
|
171
|
+
|
|
172
|
+
__observers__ = {}
|
|
173
|
+
__has_events__ = True
|
|
174
|
+
|
|
175
|
+
_booted = False
|
|
176
|
+
_scopes = {}
|
|
177
|
+
__primary_key__ = "id"
|
|
178
|
+
__primary_key_type__ = "int"
|
|
179
|
+
__casts__ = {}
|
|
180
|
+
__dates__ = []
|
|
181
|
+
__hidden__ = []
|
|
182
|
+
__relationship_hidden__ = {}
|
|
183
|
+
__visible__ = []
|
|
184
|
+
__timestamps__ = True
|
|
185
|
+
__timezone__ = "UTC"
|
|
186
|
+
__with__ = ()
|
|
187
|
+
__force_update__ = False
|
|
188
|
+
|
|
189
|
+
date_created_at = "created_at"
|
|
190
|
+
date_updated_at = "updated_at"
|
|
191
|
+
|
|
192
|
+
builder: QueryBuilder
|
|
193
|
+
|
|
194
|
+
"""Pass through will pass any method calls to the model directly through to the query builder.
|
|
195
|
+
Anytime one of these methods are called on the model it will actually be called on the query builder class.
|
|
196
|
+
"""
|
|
197
|
+
__passthrough__ = set(
|
|
198
|
+
(
|
|
199
|
+
"add_select",
|
|
200
|
+
"aggregate",
|
|
201
|
+
"all",
|
|
202
|
+
"avg",
|
|
203
|
+
"between",
|
|
204
|
+
"bulk_create",
|
|
205
|
+
"chunk",
|
|
206
|
+
"count",
|
|
207
|
+
"decrement",
|
|
208
|
+
"delete",
|
|
209
|
+
"distinct",
|
|
210
|
+
"doesnt_exist",
|
|
211
|
+
"doesnt_have",
|
|
212
|
+
"exists",
|
|
213
|
+
"find_or",
|
|
214
|
+
"find_or_404",
|
|
215
|
+
"first_or_fail",
|
|
216
|
+
"first",
|
|
217
|
+
"first_where",
|
|
218
|
+
"first_or_create",
|
|
219
|
+
"force_update",
|
|
220
|
+
"from_",
|
|
221
|
+
"from_raw",
|
|
222
|
+
"get",
|
|
223
|
+
"get_table_schema",
|
|
224
|
+
"group_by_raw",
|
|
225
|
+
"group_by",
|
|
226
|
+
"has",
|
|
227
|
+
"having",
|
|
228
|
+
"having_raw",
|
|
229
|
+
"increment",
|
|
230
|
+
"in_random_order",
|
|
231
|
+
"join_on",
|
|
232
|
+
"join",
|
|
233
|
+
"joins",
|
|
234
|
+
"last",
|
|
235
|
+
"left_join",
|
|
236
|
+
"limit",
|
|
237
|
+
"lock_for_update",
|
|
238
|
+
"make_lock",
|
|
239
|
+
"max",
|
|
240
|
+
"min",
|
|
241
|
+
"new_from_builder",
|
|
242
|
+
"new",
|
|
243
|
+
"not_between",
|
|
244
|
+
"offset",
|
|
245
|
+
"on",
|
|
246
|
+
"or_where",
|
|
247
|
+
"or_where_null",
|
|
248
|
+
"order_by_raw",
|
|
249
|
+
"order_by",
|
|
250
|
+
"paginate",
|
|
251
|
+
"right_join",
|
|
252
|
+
"select_raw",
|
|
253
|
+
"select",
|
|
254
|
+
"set_global_scope",
|
|
255
|
+
"set_schema",
|
|
256
|
+
"shared_lock",
|
|
257
|
+
"simple_paginate",
|
|
258
|
+
"skip",
|
|
259
|
+
"statement",
|
|
260
|
+
"sum",
|
|
261
|
+
"table_raw",
|
|
262
|
+
"take",
|
|
263
|
+
"to_qmark",
|
|
264
|
+
"to_sql",
|
|
265
|
+
"truncate",
|
|
266
|
+
"update",
|
|
267
|
+
"when",
|
|
268
|
+
"where_between",
|
|
269
|
+
"where_column",
|
|
270
|
+
"where_date",
|
|
271
|
+
"or_where_doesnt_have",
|
|
272
|
+
"or_has",
|
|
273
|
+
"or_where_has",
|
|
274
|
+
"or_doesnt_have",
|
|
275
|
+
"or_where_not_exists",
|
|
276
|
+
"or_where_date",
|
|
277
|
+
"where_exists",
|
|
278
|
+
"where_from_builder",
|
|
279
|
+
"where_has",
|
|
280
|
+
"where_in",
|
|
281
|
+
"where_like",
|
|
282
|
+
"where_not_between",
|
|
283
|
+
"where_not_in",
|
|
284
|
+
"where_not_like",
|
|
285
|
+
"where_not_null",
|
|
286
|
+
"where_null",
|
|
287
|
+
"where_raw",
|
|
288
|
+
"without_global_scopes",
|
|
289
|
+
"where",
|
|
290
|
+
"where_doesnt_have",
|
|
291
|
+
"with_",
|
|
292
|
+
"with_count",
|
|
293
|
+
"latest",
|
|
294
|
+
"oldest",
|
|
295
|
+
"value",
|
|
296
|
+
)
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
__cast_map__ = {}
|
|
300
|
+
|
|
301
|
+
__internal_cast_map__ = {
|
|
302
|
+
"bool": BoolCast,
|
|
303
|
+
"json": JsonCast,
|
|
304
|
+
"int": IntCast,
|
|
305
|
+
"float": FloatCast,
|
|
306
|
+
"date": DateCast,
|
|
307
|
+
"decimal": DecimalCast,
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
def __init__(self):
|
|
311
|
+
self.__attributes__ = {}
|
|
312
|
+
self.__original_attributes__ = {}
|
|
313
|
+
self.__dirty_attributes__ = {}
|
|
314
|
+
if not hasattr(self, "__appends__"):
|
|
315
|
+
self.__appends__ = []
|
|
316
|
+
self._relationships = {}
|
|
317
|
+
self._global_scopes = {}
|
|
318
|
+
|
|
319
|
+
self.boot()
|
|
320
|
+
|
|
321
|
+
@classmethod
|
|
322
|
+
def get_primary_key(self):
|
|
323
|
+
"""Gets the primary key column
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
mixed
|
|
327
|
+
"""
|
|
328
|
+
return self.__primary_key__
|
|
329
|
+
|
|
330
|
+
def get_primary_key_type(self):
|
|
331
|
+
"""Gets the primary key column type
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
mixed
|
|
335
|
+
"""
|
|
336
|
+
return self.__primary_key_type__
|
|
337
|
+
|
|
338
|
+
def get_primary_key_value(self):
|
|
339
|
+
"""Gets the primary key value.
|
|
340
|
+
|
|
341
|
+
Raises:
|
|
342
|
+
AttributeError: Raises attribute error if the model does not have an
|
|
343
|
+
attribute with the primary key.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
str|int
|
|
347
|
+
"""
|
|
348
|
+
try:
|
|
349
|
+
return getattr(self, self.get_primary_key())
|
|
350
|
+
except AttributeError:
|
|
351
|
+
name = self.__class__.__name__
|
|
352
|
+
raise AttributeError(
|
|
353
|
+
f"class '{name}' has no attribute {self.get_primary_key()}. Did you set the primary key correctly on the model using the __primary_key__ attribute?"
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
def get_foreign_key(self):
|
|
357
|
+
"""Gets the foreign key based on this model name.
|
|
358
|
+
|
|
359
|
+
Args:
|
|
360
|
+
relationship (str): The relationship name.
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
str
|
|
364
|
+
"""
|
|
365
|
+
return underscore(
|
|
366
|
+
self.__class__.__name__ + "_" + self.get_primary_key()
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
def query(self):
|
|
370
|
+
return self.get_builder()
|
|
371
|
+
|
|
372
|
+
def get_builder(self):
|
|
373
|
+
if hasattr(self, "builder"):
|
|
374
|
+
return self.builder
|
|
375
|
+
|
|
376
|
+
self.builder = QueryBuilder(
|
|
377
|
+
connection=self.__connection__,
|
|
378
|
+
table=self.get_table_name(),
|
|
379
|
+
connection_details=self.get_connection_details(),
|
|
380
|
+
model=self,
|
|
381
|
+
scopes=self._scopes.get(self.__class__),
|
|
382
|
+
dry=self.__dry__,
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
return self.builder
|
|
386
|
+
|
|
387
|
+
def get_selects(self):
|
|
388
|
+
return self.__selects__
|
|
389
|
+
|
|
390
|
+
@classmethod
|
|
391
|
+
def get_columns(cls):
|
|
392
|
+
return list(cls.first().__attributes__.keys())
|
|
393
|
+
|
|
394
|
+
def get_connection_details(self):
|
|
395
|
+
resolver = load_config().DB
|
|
396
|
+
return resolver.get_connection_details()
|
|
397
|
+
|
|
398
|
+
def boot(self):
|
|
399
|
+
if not self._booted:
|
|
400
|
+
self.observe_events(self, "booting")
|
|
401
|
+
for base_class in inspect.getmro(self.__class__):
|
|
402
|
+
class_name = base_class.__name__
|
|
403
|
+
|
|
404
|
+
if class_name.endswith("Mixin"):
|
|
405
|
+
getattr(self, "boot_" + class_name)(self.get_builder())
|
|
406
|
+
elif (
|
|
407
|
+
base_class != Model
|
|
408
|
+
and issubclass(base_class, Model)
|
|
409
|
+
and "__fillable__" in base_class.__dict__
|
|
410
|
+
and "__guarded__" in base_class.__dict__
|
|
411
|
+
):
|
|
412
|
+
raise AttributeError(
|
|
413
|
+
f"{type(self).__name__} must specify either __fillable__ or __guarded__ properties, but not both."
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
self._booted = True
|
|
417
|
+
self.observe_events(self, "booted")
|
|
418
|
+
|
|
419
|
+
self.append_passthrough(list(self.get_builder()._macros.keys()))
|
|
420
|
+
|
|
421
|
+
def append_passthrough(self, passthrough):
|
|
422
|
+
self.__passthrough__.update(passthrough)
|
|
423
|
+
return self
|
|
424
|
+
|
|
425
|
+
@classmethod
|
|
426
|
+
def get_table_name(cls):
|
|
427
|
+
"""Gets the table name.
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
str
|
|
431
|
+
"""
|
|
432
|
+
return cls.__table__ or tableize(cls.__name__)
|
|
433
|
+
|
|
434
|
+
@classmethod
|
|
435
|
+
def table(cls, table):
|
|
436
|
+
"""Gets the table name.
|
|
437
|
+
|
|
438
|
+
Returns:
|
|
439
|
+
str
|
|
440
|
+
"""
|
|
441
|
+
cls.__table__ = table
|
|
442
|
+
return cls
|
|
443
|
+
|
|
444
|
+
@classmethod
|
|
445
|
+
def find(cls, record_id, query=False):
|
|
446
|
+
"""Finds a row by the primary key ID.
|
|
447
|
+
|
|
448
|
+
Arguments:
|
|
449
|
+
record_id {int} -- The ID of the primary key to fetch.
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
Model
|
|
453
|
+
"""
|
|
454
|
+
if isinstance(record_id, (list, tuple)):
|
|
455
|
+
builder = cls().where_in(cls.get_primary_key(), record_id)
|
|
456
|
+
else:
|
|
457
|
+
builder = cls().where(cls.get_primary_key(), record_id)
|
|
458
|
+
|
|
459
|
+
if query:
|
|
460
|
+
return builder
|
|
461
|
+
else:
|
|
462
|
+
if isinstance(record_id, (list, tuple)):
|
|
463
|
+
return builder.get()
|
|
464
|
+
|
|
465
|
+
return builder.first()
|
|
466
|
+
|
|
467
|
+
@classmethod
|
|
468
|
+
def find_or_fail(cls, record_id, query=False):
|
|
469
|
+
"""Finds a row by the primary key ID or raise a ModelNotFound exception.
|
|
470
|
+
|
|
471
|
+
Arguments:
|
|
472
|
+
record_id {int} -- The ID of the primary key to fetch.
|
|
473
|
+
|
|
474
|
+
Returns:
|
|
475
|
+
Model
|
|
476
|
+
"""
|
|
477
|
+
result = cls.find(record_id, query)
|
|
478
|
+
|
|
479
|
+
if not result:
|
|
480
|
+
raise ModelNotFound()
|
|
481
|
+
|
|
482
|
+
return result
|
|
483
|
+
|
|
484
|
+
def is_loaded(self):
|
|
485
|
+
return bool(self.__attributes__)
|
|
486
|
+
|
|
487
|
+
def is_created(self):
|
|
488
|
+
return self.get_primary_key() in self.__attributes__
|
|
489
|
+
|
|
490
|
+
def add_relation(self, relations):
|
|
491
|
+
self._relationships.update(relations)
|
|
492
|
+
return self
|
|
493
|
+
|
|
494
|
+
@classmethod
|
|
495
|
+
def hydrate(cls, result, relations=None):
|
|
496
|
+
"""Takes a result and loads it into a model
|
|
497
|
+
|
|
498
|
+
Args:
|
|
499
|
+
result ([type]): [description]
|
|
500
|
+
relations (dict, optional): [description]. Defaults to {}.
|
|
501
|
+
|
|
502
|
+
Returns:
|
|
503
|
+
[type]: [description]
|
|
504
|
+
"""
|
|
505
|
+
if result is None:
|
|
506
|
+
return None
|
|
507
|
+
|
|
508
|
+
relations = relations or {}
|
|
509
|
+
if isinstance(result, (list, tuple)):
|
|
510
|
+
response = []
|
|
511
|
+
for element in result:
|
|
512
|
+
response.append(cls.hydrate(element))
|
|
513
|
+
return cls.new_collection(response)
|
|
514
|
+
|
|
515
|
+
elif isinstance(result, dict):
|
|
516
|
+
model = cls()
|
|
517
|
+
dic = {}
|
|
518
|
+
for key, value in result.items():
|
|
519
|
+
if key in model.get_dates() and value:
|
|
520
|
+
value = model.get_new_date(value)
|
|
521
|
+
dic.update({key: value})
|
|
522
|
+
|
|
523
|
+
logger = logging.getLogger("masoniteorm.models.hydrate")
|
|
524
|
+
logger.setLevel(logging.INFO)
|
|
525
|
+
logger.propagate = False
|
|
526
|
+
logger.info(
|
|
527
|
+
f"Hydrating Model {cls.__name__}",
|
|
528
|
+
extra={
|
|
529
|
+
"class_name": cls.__name__,
|
|
530
|
+
"class_module": cls.__module__,
|
|
531
|
+
},
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
model.observe_events(model, "hydrating")
|
|
535
|
+
model.__attributes__.update(dic or {})
|
|
536
|
+
model.__original_attributes__.update(dic or {})
|
|
537
|
+
model.add_relation(relations)
|
|
538
|
+
model.observe_events(model, "hydrated")
|
|
539
|
+
return model
|
|
540
|
+
|
|
541
|
+
elif hasattr(result, "serialize"):
|
|
542
|
+
model = cls()
|
|
543
|
+
model.__attributes__.update(result.serialize())
|
|
544
|
+
model.__original_attributes__.update(result.serialize())
|
|
545
|
+
return model
|
|
546
|
+
else:
|
|
547
|
+
model = cls()
|
|
548
|
+
model.observe_events(model, "hydrating")
|
|
549
|
+
model.__attributes__.update(dict(result))
|
|
550
|
+
model.__original_attributes__.update(dict(result))
|
|
551
|
+
model.observe_events(model, "hydrated")
|
|
552
|
+
return model
|
|
553
|
+
|
|
554
|
+
def fill(self, attributes):
|
|
555
|
+
self.__attributes__.update(attributes)
|
|
556
|
+
return self
|
|
557
|
+
|
|
558
|
+
def fill_original(self, attributes):
|
|
559
|
+
self.__original_attributes__.update(attributes)
|
|
560
|
+
return self
|
|
561
|
+
|
|
562
|
+
@classmethod
|
|
563
|
+
def new_collection(cls, data):
|
|
564
|
+
"""Takes a result and puts it into a new collection.
|
|
565
|
+
This is designed to be able to be overidden by the user.
|
|
566
|
+
|
|
567
|
+
Args:
|
|
568
|
+
data (list|dict): Could be any data type but will be loaded directly into a collection.
|
|
569
|
+
|
|
570
|
+
Returns:
|
|
571
|
+
Collection
|
|
572
|
+
"""
|
|
573
|
+
return Collection(data)
|
|
574
|
+
|
|
575
|
+
@classmethod
|
|
576
|
+
def create(cls, dictionary=None, query=False, cast=True, **kwargs):
|
|
577
|
+
"""Creates new records based off of a dictionary as well as data set on the model
|
|
578
|
+
such as fillable values.
|
|
579
|
+
|
|
580
|
+
Args:
|
|
581
|
+
dictionary (dict, optional): [description]. Defaults to {}.
|
|
582
|
+
query (bool, optional): [description]. Defaults to False.
|
|
583
|
+
cast (bool, optional): [description]. Whether or not to cast passed values.
|
|
584
|
+
|
|
585
|
+
Returns:
|
|
586
|
+
self: A hydrated version of a model
|
|
587
|
+
"""
|
|
588
|
+
if query:
|
|
589
|
+
return cls.builder.create(
|
|
590
|
+
dictionary, query=True, cast=cast, **kwargs
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
return cls.builder.create(dictionary, cast=cast, **kwargs)
|
|
594
|
+
|
|
595
|
+
def cast_value(self, attribute: str, value: Any):
|
|
596
|
+
"""
|
|
597
|
+
Given an attribute name and a value, casts the value using the model's registered caster.
|
|
598
|
+
If no registered caster exists, returns the unmodified value.
|
|
599
|
+
"""
|
|
600
|
+
if value is None:
|
|
601
|
+
return None
|
|
602
|
+
|
|
603
|
+
cast_method = self.__casts__.get(attribute)
|
|
604
|
+
|
|
605
|
+
if isinstance(cast_method, str):
|
|
606
|
+
cast_map = self.get_cast_map()
|
|
607
|
+
return cast_map[cast_method]().set(value)
|
|
608
|
+
|
|
609
|
+
if cast_method:
|
|
610
|
+
return cast_method(value)
|
|
611
|
+
|
|
612
|
+
return value
|
|
613
|
+
|
|
614
|
+
def cast_values(self, attributes: Dict[str, Any]) -> Dict[str, Any]:
|
|
615
|
+
"""
|
|
616
|
+
Runs provided dictionary through all model casters and returns the result.
|
|
617
|
+
|
|
618
|
+
Does not mutate the passed dictionary.
|
|
619
|
+
"""
|
|
620
|
+
updated_attribs = {}
|
|
621
|
+
for key, value in attributes.items():
|
|
622
|
+
if key in self.get_dates():
|
|
623
|
+
updated_attribs.update(
|
|
624
|
+
{key: self.get_new_datetime_string(value)}
|
|
625
|
+
)
|
|
626
|
+
elif key in self.__casts__:
|
|
627
|
+
updated_attribs.update({key: self.cast_value(key, value)})
|
|
628
|
+
else:
|
|
629
|
+
updated_attribs.update({key: value})
|
|
630
|
+
|
|
631
|
+
return updated_attribs
|
|
632
|
+
|
|
633
|
+
def fresh(self):
|
|
634
|
+
return (
|
|
635
|
+
self.get_builder()
|
|
636
|
+
.where(self.get_primary_key(), self.get_primary_key_value())
|
|
637
|
+
.first()
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
def serialize(self, exclude=None, include=None):
|
|
641
|
+
"""Takes the data as a model and converts it into a dictionary.
|
|
642
|
+
|
|
643
|
+
Returns:
|
|
644
|
+
dict
|
|
645
|
+
"""
|
|
646
|
+
serialized_dictionary = self.__attributes__.copy()
|
|
647
|
+
|
|
648
|
+
# prevent using both exclude and include at the same time
|
|
649
|
+
if exclude is not None and include is not None:
|
|
650
|
+
raise AttributeError(
|
|
651
|
+
"Can not define both includes and exclude values."
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
if exclude is not None:
|
|
655
|
+
self.__hidden__ = exclude
|
|
656
|
+
|
|
657
|
+
if include is not None:
|
|
658
|
+
self.__visible__ = include
|
|
659
|
+
|
|
660
|
+
# prevent using both hidden and visible at the same time
|
|
661
|
+
if self.__visible__ and self.__hidden__:
|
|
662
|
+
raise AttributeError(
|
|
663
|
+
f"class model '{self.__class__.__name__}' defines both __visible__ and __hidden__."
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
if self.__visible__:
|
|
667
|
+
new_serialized_dictionary = {
|
|
668
|
+
k: serialized_dictionary[k]
|
|
669
|
+
for k in self.__visible__
|
|
670
|
+
if k in serialized_dictionary
|
|
671
|
+
}
|
|
672
|
+
serialized_dictionary = new_serialized_dictionary
|
|
673
|
+
else:
|
|
674
|
+
for key in self.__hidden__:
|
|
675
|
+
if key in serialized_dictionary:
|
|
676
|
+
serialized_dictionary.pop(key)
|
|
677
|
+
|
|
678
|
+
for date_column in self.get_dates():
|
|
679
|
+
if (
|
|
680
|
+
date_column in serialized_dictionary
|
|
681
|
+
and serialized_dictionary[date_column]
|
|
682
|
+
):
|
|
683
|
+
serialized_dictionary[date_column] = (
|
|
684
|
+
self.get_new_serialized_date(
|
|
685
|
+
serialized_dictionary[date_column]
|
|
686
|
+
)
|
|
687
|
+
)
|
|
688
|
+
|
|
689
|
+
serialized_dictionary.update(self.__dirty_attributes__)
|
|
690
|
+
|
|
691
|
+
# The builder is inside the attributes but should not be serialized
|
|
692
|
+
if "builder" in serialized_dictionary:
|
|
693
|
+
serialized_dictionary.pop("builder")
|
|
694
|
+
|
|
695
|
+
# Serialize relationships as well
|
|
696
|
+
serialized_dictionary.update(self.relations_to_dict())
|
|
697
|
+
|
|
698
|
+
for append in self.__appends__:
|
|
699
|
+
serialized_dictionary.update({append: getattr(self, append)})
|
|
700
|
+
|
|
701
|
+
remove_keys = []
|
|
702
|
+
for key, value in serialized_dictionary.items():
|
|
703
|
+
if key in self.__hidden__:
|
|
704
|
+
remove_keys.append(key)
|
|
705
|
+
if hasattr(value, "serialize"):
|
|
706
|
+
value = value.serialize(
|
|
707
|
+
self.__relationship_hidden__.get(key, [])
|
|
708
|
+
)
|
|
709
|
+
if isinstance(value, datetime):
|
|
710
|
+
value = self.get_new_serialized_date(value)
|
|
711
|
+
if key in self.__casts__:
|
|
712
|
+
value = self._uncast_value(key, value)
|
|
713
|
+
|
|
714
|
+
serialized_dictionary.update({key: value})
|
|
715
|
+
|
|
716
|
+
for key in remove_keys:
|
|
717
|
+
serialized_dictionary.pop(key)
|
|
718
|
+
|
|
719
|
+
return serialized_dictionary
|
|
720
|
+
|
|
721
|
+
def to_json(self):
|
|
722
|
+
"""Converts a model to JSON
|
|
723
|
+
|
|
724
|
+
Returns:
|
|
725
|
+
string
|
|
726
|
+
"""
|
|
727
|
+
return json.dumps(self.serialize(), default=str)
|
|
728
|
+
|
|
729
|
+
@classmethod
|
|
730
|
+
def first_or_create(cls, wheres, creates: dict = None):
|
|
731
|
+
"""Get the first record matching the attributes or create it.
|
|
732
|
+
|
|
733
|
+
Returns:
|
|
734
|
+
Model
|
|
735
|
+
"""
|
|
736
|
+
if creates is None:
|
|
737
|
+
creates = {}
|
|
738
|
+
self = cls()
|
|
739
|
+
record = self.where(wheres).first()
|
|
740
|
+
total = {}
|
|
741
|
+
total.update(creates)
|
|
742
|
+
total.update(wheres)
|
|
743
|
+
if not record:
|
|
744
|
+
return self.create(total, id_key=cls.get_primary_key())
|
|
745
|
+
return record
|
|
746
|
+
|
|
747
|
+
@classmethod
|
|
748
|
+
def update_or_create(cls, wheres, updates):
|
|
749
|
+
self = cls()
|
|
750
|
+
record = self.where(wheres).first()
|
|
751
|
+
total = {}
|
|
752
|
+
total.update(updates)
|
|
753
|
+
total.update(wheres)
|
|
754
|
+
if not record:
|
|
755
|
+
return self.create(total, id_key=cls.get_primary_key()).fresh()
|
|
756
|
+
|
|
757
|
+
return self.where(wheres).update(total)
|
|
758
|
+
|
|
759
|
+
def relations_to_dict(self):
|
|
760
|
+
"""Converts a models relationships to a dictionary
|
|
761
|
+
|
|
762
|
+
Returns:
|
|
763
|
+
[type]: [description]
|
|
764
|
+
"""
|
|
765
|
+
new_dic = {}
|
|
766
|
+
for key, value in self._relationships.items():
|
|
767
|
+
if value == {}:
|
|
768
|
+
new_dic.update({key: {}})
|
|
769
|
+
else:
|
|
770
|
+
if value is None:
|
|
771
|
+
new_dic.update({key: {}})
|
|
772
|
+
continue
|
|
773
|
+
elif isinstance(value, list):
|
|
774
|
+
value = Collection(value).serialize()
|
|
775
|
+
elif isinstance(value, dict):
|
|
776
|
+
pass
|
|
777
|
+
else:
|
|
778
|
+
value = value.serialize()
|
|
779
|
+
|
|
780
|
+
new_dic.update({key: value})
|
|
781
|
+
|
|
782
|
+
return new_dic
|
|
783
|
+
|
|
784
|
+
def touch(self, date=None, query=True):
|
|
785
|
+
"""Updates the current timestamps on the model"""
|
|
786
|
+
|
|
787
|
+
if not self.__timestamps__:
|
|
788
|
+
return False
|
|
789
|
+
|
|
790
|
+
self._update_timestamps(date=date)
|
|
791
|
+
|
|
792
|
+
return self.save(query=query)
|
|
793
|
+
|
|
794
|
+
def _update_timestamps(self, date=None):
|
|
795
|
+
"""Sets the updated at date to the current time or a specified date
|
|
796
|
+
|
|
797
|
+
Args:
|
|
798
|
+
date (datetime.datetime, optional): a date. If none is specified then it will use the current date Defaults to None.
|
|
799
|
+
"""
|
|
800
|
+
self.updated_at = date or self._current_timestamp()
|
|
801
|
+
|
|
802
|
+
def _current_timestamp(self):
|
|
803
|
+
return datetime.now()
|
|
804
|
+
|
|
805
|
+
def __getattr__(self, attribute):
|
|
806
|
+
"""Magic method that is called when an attribute does not exist on the model.
|
|
807
|
+
|
|
808
|
+
Args:
|
|
809
|
+
attribute (string): the name of the attribute being accessed or called.
|
|
810
|
+
|
|
811
|
+
Returns:
|
|
812
|
+
mixed: Could be anything that a method can return.
|
|
813
|
+
"""
|
|
814
|
+
|
|
815
|
+
new_name_accessor = "get_" + attribute + "_attribute"
|
|
816
|
+
|
|
817
|
+
if (new_name_accessor) in self.__class__.__dict__:
|
|
818
|
+
return self.__class__.__dict__.get(new_name_accessor)(self)
|
|
819
|
+
|
|
820
|
+
if (
|
|
821
|
+
"__dirty_attributes__" in self.__dict__
|
|
822
|
+
and attribute in self.__dict__["__dirty_attributes__"]
|
|
823
|
+
):
|
|
824
|
+
return self.get_dirty_value(attribute)
|
|
825
|
+
|
|
826
|
+
if (
|
|
827
|
+
"__attributes__" in self.__dict__
|
|
828
|
+
and attribute in self.__dict__["__attributes__"]
|
|
829
|
+
):
|
|
830
|
+
if attribute in self.get_dates():
|
|
831
|
+
return (
|
|
832
|
+
self.get_new_date(self.get_value(attribute))
|
|
833
|
+
if self.get_value(attribute)
|
|
834
|
+
else None
|
|
835
|
+
)
|
|
836
|
+
return self.get_value(attribute)
|
|
837
|
+
|
|
838
|
+
if attribute in self.__passthrough__:
|
|
839
|
+
|
|
840
|
+
def method(*args, **kwargs):
|
|
841
|
+
return getattr(self.get_builder(), attribute)(*args, **kwargs)
|
|
842
|
+
|
|
843
|
+
return method
|
|
844
|
+
|
|
845
|
+
if attribute in self.__dict__.get("_relationships", {}):
|
|
846
|
+
return self.__dict__["_relationships"][attribute]
|
|
847
|
+
|
|
848
|
+
if attribute not in self.__dict__:
|
|
849
|
+
name = self.__class__.__name__
|
|
850
|
+
|
|
851
|
+
raise AttributeError(
|
|
852
|
+
f"class model '{name}' has no attribute {attribute}"
|
|
853
|
+
)
|
|
854
|
+
|
|
855
|
+
return None
|
|
856
|
+
|
|
857
|
+
def only(self, attributes: list) -> dict:
|
|
858
|
+
if isinstance(attributes, str):
|
|
859
|
+
attributes = [attributes]
|
|
860
|
+
results: dict[str, Any] = {}
|
|
861
|
+
for attribute in attributes:
|
|
862
|
+
if " as " in attribute:
|
|
863
|
+
attribute, alias = attribute.split(" as ")
|
|
864
|
+
alias = alias.strip()
|
|
865
|
+
attribute = attribute.strip()
|
|
866
|
+
else:
|
|
867
|
+
alias = attribute.strip()
|
|
868
|
+
attribute = attribute.strip()
|
|
869
|
+
|
|
870
|
+
results[alias] = self.get_raw_attribute(attribute)
|
|
871
|
+
|
|
872
|
+
return results
|
|
873
|
+
|
|
874
|
+
def __setattr__(self, attribute, value):
|
|
875
|
+
if hasattr(self, "set_" + attribute + "_attribute"):
|
|
876
|
+
method = getattr(self, "set_" + attribute + "_attribute")
|
|
877
|
+
value = method(value)
|
|
878
|
+
|
|
879
|
+
if attribute in self.__casts__:
|
|
880
|
+
value = self.cast_value(attribute, value)
|
|
881
|
+
|
|
882
|
+
if attribute in self.get_dates():
|
|
883
|
+
value = self.get_new_datetime_string(value)
|
|
884
|
+
|
|
885
|
+
try:
|
|
886
|
+
if not attribute.startswith("_"):
|
|
887
|
+
self.__dict__["__dirty_attributes__"].update(
|
|
888
|
+
{attribute: value}
|
|
889
|
+
)
|
|
890
|
+
else:
|
|
891
|
+
self.__dict__[attribute] = value
|
|
892
|
+
except KeyError:
|
|
893
|
+
pass
|
|
894
|
+
|
|
895
|
+
def get_raw_attribute(self, attribute):
|
|
896
|
+
"""Gets an attribute without having to call the models magic methods. Gets around infinite recursion loops.
|
|
897
|
+
|
|
898
|
+
Args:
|
|
899
|
+
attribute (string): The attribute to fetch
|
|
900
|
+
|
|
901
|
+
Returns:
|
|
902
|
+
mixed: Any value an attribute can be.
|
|
903
|
+
"""
|
|
904
|
+
return self.__attributes__.get(attribute)
|
|
905
|
+
|
|
906
|
+
def is_dirty(self):
|
|
907
|
+
return bool(self.__dirty_attributes__)
|
|
908
|
+
|
|
909
|
+
def get_original(self, key):
|
|
910
|
+
return self.__original_attributes__.get(key)
|
|
911
|
+
|
|
912
|
+
def get_dirty(self, key):
|
|
913
|
+
return self.__dirty_attributes__.get(key)
|
|
914
|
+
|
|
915
|
+
def get_dirty_keys(self):
|
|
916
|
+
return list(self.get_dirty_attributes().keys())
|
|
917
|
+
|
|
918
|
+
def save(self, query=False):
|
|
919
|
+
builder = self.get_builder()
|
|
920
|
+
|
|
921
|
+
if "builder" in self.__dirty_attributes__:
|
|
922
|
+
self.__dirty_attributes__.pop("builder")
|
|
923
|
+
|
|
924
|
+
self.observe_events(self, "saving")
|
|
925
|
+
|
|
926
|
+
if not query:
|
|
927
|
+
if self.is_loaded():
|
|
928
|
+
result = builder.update(
|
|
929
|
+
self.__dirty_attributes__, ignore_mass_assignment=True
|
|
930
|
+
)
|
|
931
|
+
else:
|
|
932
|
+
result = self.create(
|
|
933
|
+
self.__dirty_attributes__,
|
|
934
|
+
query=query,
|
|
935
|
+
id_key=self.get_primary_key(),
|
|
936
|
+
ignore_mass_assignment=True,
|
|
937
|
+
)
|
|
938
|
+
self.observe_events(self, "saved")
|
|
939
|
+
self.__dirty_attributes__ = {}
|
|
940
|
+
if self.is_loaded():
|
|
941
|
+
return self
|
|
942
|
+
return result
|
|
943
|
+
|
|
944
|
+
if self.is_loaded():
|
|
945
|
+
result = builder.update(
|
|
946
|
+
self.__dirty_attributes__,
|
|
947
|
+
dry=query,
|
|
948
|
+
ignore_mass_assignment=True,
|
|
949
|
+
)
|
|
950
|
+
else:
|
|
951
|
+
result = self.create(self.__dirty_attributes__, query=query)
|
|
952
|
+
|
|
953
|
+
return result
|
|
954
|
+
|
|
955
|
+
def get_value(self, attribute):
|
|
956
|
+
value = self.__attributes__[attribute]
|
|
957
|
+
if attribute in self.__casts__:
|
|
958
|
+
return self._uncast_value(attribute, value)
|
|
959
|
+
|
|
960
|
+
return value
|
|
961
|
+
|
|
962
|
+
def get_dirty_value(self, attribute):
|
|
963
|
+
value = self.__dirty_attributes__[attribute]
|
|
964
|
+
if attribute in self.__casts__:
|
|
965
|
+
return self._uncast_value(attribute, value)
|
|
966
|
+
|
|
967
|
+
return value
|
|
968
|
+
|
|
969
|
+
def all_attributes(self):
|
|
970
|
+
attributes = self.__attributes__
|
|
971
|
+
attributes.update(self.get_dirty_attributes())
|
|
972
|
+
for key, value in attributes.items():
|
|
973
|
+
if key in self.__casts__:
|
|
974
|
+
attributes.update({key: self._uncast_value(key, value)})
|
|
975
|
+
|
|
976
|
+
return attributes
|
|
977
|
+
|
|
978
|
+
def delete_attribute(self, key):
|
|
979
|
+
if key in self.__attributes__:
|
|
980
|
+
del self.__attributes__[key]
|
|
981
|
+
return True
|
|
982
|
+
|
|
983
|
+
return False
|
|
984
|
+
|
|
985
|
+
def get_dirty_attributes(self):
|
|
986
|
+
if "builder" in self.__dirty_attributes__:
|
|
987
|
+
self.__dirty_attributes__.pop("builder")
|
|
988
|
+
return self.__dirty_attributes__ or {}
|
|
989
|
+
|
|
990
|
+
def get_cast_map(self):
|
|
991
|
+
cast_map = self.__internal_cast_map__
|
|
992
|
+
cast_map.update(self.__cast_map__)
|
|
993
|
+
return cast_map
|
|
994
|
+
|
|
995
|
+
def _uncast_value(self, attribute, value):
|
|
996
|
+
if value is None:
|
|
997
|
+
return None
|
|
998
|
+
|
|
999
|
+
cast_method = self.__casts__[attribute]
|
|
1000
|
+
|
|
1001
|
+
if isinstance(cast_method, str):
|
|
1002
|
+
cast_map = self.get_cast_map()
|
|
1003
|
+
return cast_map[cast_method]().get(value)
|
|
1004
|
+
|
|
1005
|
+
if cast_method:
|
|
1006
|
+
return cast_method(value)
|
|
1007
|
+
|
|
1008
|
+
return value
|
|
1009
|
+
|
|
1010
|
+
@classmethod
|
|
1011
|
+
def load(cls, *loads):
|
|
1012
|
+
cls.boot()
|
|
1013
|
+
cls._loads += loads
|
|
1014
|
+
return cls.builder
|
|
1015
|
+
|
|
1016
|
+
def __getitem__(self, attribute):
|
|
1017
|
+
return getattr(self, attribute)
|
|
1018
|
+
|
|
1019
|
+
def get_dates(self):
|
|
1020
|
+
"""
|
|
1021
|
+
Get the attributes that should be converted to dates.
|
|
1022
|
+
|
|
1023
|
+
:rtype: list
|
|
1024
|
+
"""
|
|
1025
|
+
defaults = [self.date_created_at, self.date_updated_at]
|
|
1026
|
+
|
|
1027
|
+
return self.__dates__ + defaults
|
|
1028
|
+
|
|
1029
|
+
def get_new_date(self, _datetime=None):
|
|
1030
|
+
"""
|
|
1031
|
+
Get the attributes that should be converted to dates.
|
|
1032
|
+
|
|
1033
|
+
:rtype: list
|
|
1034
|
+
"""
|
|
1035
|
+
import pendulum
|
|
1036
|
+
|
|
1037
|
+
if not _datetime:
|
|
1038
|
+
return pendulum.now(tz=self.__timezone__)
|
|
1039
|
+
elif isinstance(_datetime, str):
|
|
1040
|
+
return pendulum.parse(_datetime, tz=self.__timezone__)
|
|
1041
|
+
elif isinstance(_datetime, datetime):
|
|
1042
|
+
return pendulum.instance(_datetime, tz=self.__timezone__)
|
|
1043
|
+
elif isinstance(_datetime, datetimedate):
|
|
1044
|
+
return pendulum.datetime(
|
|
1045
|
+
_datetime.year,
|
|
1046
|
+
_datetime.month,
|
|
1047
|
+
_datetime.day,
|
|
1048
|
+
tz=self.__timezone__,
|
|
1049
|
+
)
|
|
1050
|
+
elif isinstance(_datetime, datetimetime):
|
|
1051
|
+
return pendulum.parse(
|
|
1052
|
+
f"{_datetime.hour}:{_datetime.minute}:{_datetime.second}",
|
|
1053
|
+
tz=self.__timezone__,
|
|
1054
|
+
)
|
|
1055
|
+
|
|
1056
|
+
return pendulum.instance(_datetime, tz=self.__timezone__)
|
|
1057
|
+
|
|
1058
|
+
def get_new_datetime_string(self, _datetime=None):
|
|
1059
|
+
"""
|
|
1060
|
+
Given an optional datetime value, constructs and returns a new datetime string.
|
|
1061
|
+
If no datetime is specified, returns the current time.
|
|
1062
|
+
|
|
1063
|
+
:rtype: list
|
|
1064
|
+
"""
|
|
1065
|
+
return self.get_new_date(_datetime).to_datetime_string()
|
|
1066
|
+
|
|
1067
|
+
def get_new_serialized_date(self, _datetime):
|
|
1068
|
+
"""
|
|
1069
|
+
Get the attributes that should be converted to dates.
|
|
1070
|
+
|
|
1071
|
+
:rtype: list
|
|
1072
|
+
"""
|
|
1073
|
+
return self.get_new_date(_datetime).isoformat()
|
|
1074
|
+
|
|
1075
|
+
def set_appends(self, appends):
|
|
1076
|
+
"""
|
|
1077
|
+
Get the attributes that should be converted to dates.
|
|
1078
|
+
|
|
1079
|
+
:rtype: list
|
|
1080
|
+
"""
|
|
1081
|
+
self.__appends__ += appends
|
|
1082
|
+
return self
|
|
1083
|
+
|
|
1084
|
+
def save_many(self, relation, relating_records):
|
|
1085
|
+
if isinstance(relating_records, Model):
|
|
1086
|
+
raise ValueError(
|
|
1087
|
+
"Saving many records requires an iterable like a collection or a list of models and not a Model object. To attach a model, use the 'attach' method."
|
|
1088
|
+
)
|
|
1089
|
+
|
|
1090
|
+
for related_record in relating_records:
|
|
1091
|
+
self.attach(relation, related_record)
|
|
1092
|
+
|
|
1093
|
+
def detach_many(self, relation, relating_records):
|
|
1094
|
+
if isinstance(relating_records, Model):
|
|
1095
|
+
raise ValueError(
|
|
1096
|
+
"Detaching many records requires an iterable like a collection or a list of models and not a Model object. To detach a model, use the 'detach' method."
|
|
1097
|
+
)
|
|
1098
|
+
|
|
1099
|
+
related = getattr(self.__class__, relation)
|
|
1100
|
+
for related_record in relating_records:
|
|
1101
|
+
if not related_record.is_created():
|
|
1102
|
+
related_record.create(related_record.all_attributes())
|
|
1103
|
+
else:
|
|
1104
|
+
related_record.save()
|
|
1105
|
+
|
|
1106
|
+
related.detach(self, related_record)
|
|
1107
|
+
|
|
1108
|
+
def related(self, relation):
|
|
1109
|
+
related = getattr(self.__class__, relation)
|
|
1110
|
+
return related.relate(self)
|
|
1111
|
+
|
|
1112
|
+
def get_related(self, relation):
|
|
1113
|
+
related = getattr(self.__class__, relation)
|
|
1114
|
+
return related
|
|
1115
|
+
|
|
1116
|
+
def attach(self, relation, related_record):
|
|
1117
|
+
related = getattr(self.__class__, relation)
|
|
1118
|
+
return related.attach(self, related_record)
|
|
1119
|
+
|
|
1120
|
+
def detach(self, relation, related_record):
|
|
1121
|
+
related = getattr(self.__class__, relation)
|
|
1122
|
+
|
|
1123
|
+
if not related_record.is_created():
|
|
1124
|
+
related_record = related_record.create(
|
|
1125
|
+
related_record.all_attributes()
|
|
1126
|
+
)
|
|
1127
|
+
else:
|
|
1128
|
+
related_record.save()
|
|
1129
|
+
|
|
1130
|
+
return related.detach(self, related_record)
|
|
1131
|
+
|
|
1132
|
+
def save_quietly(self):
|
|
1133
|
+
"""This method calls the save method on a model without firing the saved & saving observer events. Saved/Saving
|
|
1134
|
+
are toggled back on once save_quietly has been ran.
|
|
1135
|
+
|
|
1136
|
+
Instead of calling:
|
|
1137
|
+
|
|
1138
|
+
User().save(...)
|
|
1139
|
+
|
|
1140
|
+
you can use this:
|
|
1141
|
+
|
|
1142
|
+
User.save_quietly(...)
|
|
1143
|
+
"""
|
|
1144
|
+
self.without_events()
|
|
1145
|
+
saved = self.save()
|
|
1146
|
+
self.with_events()
|
|
1147
|
+
return saved
|
|
1148
|
+
|
|
1149
|
+
def delete_quietly(self):
|
|
1150
|
+
"""This method calls the delete method on a model without firing the delete & deleting observer events.
|
|
1151
|
+
Instead of calling:
|
|
1152
|
+
|
|
1153
|
+
User().delete(...)
|
|
1154
|
+
|
|
1155
|
+
you can use this:
|
|
1156
|
+
|
|
1157
|
+
User.delete_quietly(...)
|
|
1158
|
+
|
|
1159
|
+
Returns:
|
|
1160
|
+
self
|
|
1161
|
+
"""
|
|
1162
|
+
delete = (
|
|
1163
|
+
self.without_events()
|
|
1164
|
+
.where(self.get_primary_key(), self.get_primary_key_value())
|
|
1165
|
+
.delete()
|
|
1166
|
+
)
|
|
1167
|
+
self.with_events()
|
|
1168
|
+
return delete
|
|
1169
|
+
|
|
1170
|
+
def attach_related(self, relation, related_record):
|
|
1171
|
+
return self.attach(relation, related_record)
|
|
1172
|
+
|
|
1173
|
+
@classmethod
|
|
1174
|
+
def filter_fillable(cls, dictionary: Dict[str, Any]) -> Dict[str, Any]:
|
|
1175
|
+
"""
|
|
1176
|
+
Filters provided dictionary to only include fields specified in the model's __fillable__ property
|
|
1177
|
+
|
|
1178
|
+
Passed dictionary is not mutated.
|
|
1179
|
+
"""
|
|
1180
|
+
if cls.__fillable__ != ["*"]:
|
|
1181
|
+
dictionary = {
|
|
1182
|
+
x: dictionary[x] for x in cls.__fillable__ if x in dictionary
|
|
1183
|
+
}
|
|
1184
|
+
return dictionary
|
|
1185
|
+
|
|
1186
|
+
@classmethod
|
|
1187
|
+
def filter_mass_assignment(
|
|
1188
|
+
cls, dictionary: Dict[str, Any]
|
|
1189
|
+
) -> Dict[str, Any]:
|
|
1190
|
+
"""
|
|
1191
|
+
Filters the provided dictionary in preparation for a mass-assignment operation
|
|
1192
|
+
|
|
1193
|
+
Wrapper around filter_fillable() & filter_guarded(). Passed dictionary is not mutated.
|
|
1194
|
+
"""
|
|
1195
|
+
return cls.filter_guarded(cls.filter_fillable(dictionary))
|
|
1196
|
+
|
|
1197
|
+
@classmethod
|
|
1198
|
+
def filter_guarded(cls, dictionary: Dict[str, Any]) -> Dict[str, Any]:
|
|
1199
|
+
"""
|
|
1200
|
+
Filters provided dictionary to exclude fields specified in the model's __guarded__ property
|
|
1201
|
+
|
|
1202
|
+
Passed dictionary is not mutated.
|
|
1203
|
+
"""
|
|
1204
|
+
if cls.__guarded__ == ["*"]:
|
|
1205
|
+
# If all fields are guarded, all data should be filtered
|
|
1206
|
+
return {}
|
|
1207
|
+
return {
|
|
1208
|
+
f: dictionary[f] for f in dictionary if f not in cls.__guarded__
|
|
1209
|
+
}
|