database-wrapper 0.1.86__py3-none-any.whl → 0.2.2__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.
- database_wrapper/config.py +3 -3
- database_wrapper/db_backend.py +41 -40
- database_wrapper/db_data_model.py +102 -102
- database_wrapper/db_wrapper.py +179 -159
- database_wrapper/db_wrapper_async.py +177 -165
- database_wrapper/db_wrapper_mixin.py +72 -72
- database_wrapper/serialization.py +15 -17
- database_wrapper/utils/dataclass_addons.py +5 -5
- {database_wrapper-0.1.86.dist-info → database_wrapper-0.2.2.dist-info}/METADATA +5 -5
- database_wrapper-0.2.2.dist-info/RECORD +17 -0
- {database_wrapper-0.1.86.dist-info → database_wrapper-0.2.2.dist-info}/WHEEL +1 -1
- database_wrapper-0.1.86.dist-info/RECORD +0 -17
- {database_wrapper-0.1.86.dist-info → database_wrapper-0.2.2.dist-info}/top_level.txt +0 -0
database_wrapper/db_wrapper.py
CHANGED
|
@@ -15,200 +15,218 @@ class DBWrapper(DBWrapperMixin):
|
|
|
15
15
|
#####################
|
|
16
16
|
|
|
17
17
|
# Action methods
|
|
18
|
-
def
|
|
18
|
+
def get_one(
|
|
19
19
|
self,
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
empty_data_class: DataModelType,
|
|
21
|
+
custom_query: Any = None,
|
|
22
22
|
) -> DataModelType | None:
|
|
23
23
|
"""
|
|
24
24
|
Retrieves a single record from the database by class defined id.
|
|
25
25
|
|
|
26
26
|
Args:
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
empty_data_class (DataModelType): The data model to use for the query.
|
|
28
|
+
custom_query (Any, optional): The custom query to use for the query. Defaults to None.
|
|
29
29
|
|
|
30
30
|
Returns:
|
|
31
31
|
DataModelType | None: The result of the query.
|
|
32
32
|
"""
|
|
33
33
|
# Figure out the id key and value
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if not
|
|
34
|
+
id_key = empty_data_class.id_key
|
|
35
|
+
id_value = empty_data_class.id
|
|
36
|
+
if not id_key:
|
|
37
37
|
raise ValueError("Id key is not set")
|
|
38
|
-
if not
|
|
38
|
+
if not id_value:
|
|
39
39
|
raise ValueError("Id value is not set")
|
|
40
40
|
|
|
41
41
|
# Get the record
|
|
42
|
-
res = self.
|
|
43
|
-
|
|
42
|
+
res = self.get_all(
|
|
43
|
+
empty_data_class,
|
|
44
|
+
id_key,
|
|
45
|
+
id_value,
|
|
46
|
+
limit=1,
|
|
47
|
+
custom_query=custom_query,
|
|
44
48
|
)
|
|
45
49
|
for row in res:
|
|
46
50
|
return row
|
|
47
51
|
else:
|
|
48
52
|
return None
|
|
49
53
|
|
|
50
|
-
def
|
|
54
|
+
def get_by_key(
|
|
51
55
|
self,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
empty_data_class: DataModelType,
|
|
57
|
+
id_key: str,
|
|
58
|
+
id_value: Any,
|
|
59
|
+
custom_query: Any = None,
|
|
56
60
|
) -> DataModelType | None:
|
|
57
61
|
"""
|
|
58
62
|
Retrieves a single record from the database using the given key.
|
|
59
63
|
|
|
60
64
|
Args:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
empty_data_class (DataModelType): The data model to use for the query.
|
|
66
|
+
id_key (str): The name of the key to use for the query.
|
|
67
|
+
id_value (Any): The value of the key to use for the query.
|
|
68
|
+
custom_query (Any, optional): The custom query to use for the query. Defaults to None.
|
|
65
69
|
|
|
66
70
|
Returns:
|
|
67
71
|
DataModelType | None: The result of the query.
|
|
68
72
|
"""
|
|
69
73
|
# Get the record
|
|
70
|
-
res = self.
|
|
71
|
-
|
|
74
|
+
res = self.get_all(
|
|
75
|
+
empty_data_class,
|
|
76
|
+
id_key,
|
|
77
|
+
id_value,
|
|
78
|
+
limit=1,
|
|
79
|
+
custom_query=custom_query,
|
|
72
80
|
)
|
|
73
81
|
for row in res:
|
|
74
82
|
return row
|
|
75
83
|
else:
|
|
76
84
|
return None
|
|
77
85
|
|
|
78
|
-
def
|
|
86
|
+
def get_all(
|
|
79
87
|
self,
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
88
|
+
empty_data_class: DataModelType,
|
|
89
|
+
id_key: str | None = None,
|
|
90
|
+
id_value: Any | None = None,
|
|
91
|
+
order_by: OrderByItem | None = None,
|
|
84
92
|
offset: int = 0,
|
|
85
93
|
limit: int = 100,
|
|
86
|
-
|
|
94
|
+
custom_query: Any = None,
|
|
87
95
|
) -> Generator[DataModelType, None, None]:
|
|
88
96
|
"""
|
|
89
97
|
Retrieves all records from the database.
|
|
90
98
|
|
|
91
99
|
Args:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
empty_data_class (DataModelType): The data model to use for the query.
|
|
101
|
+
id_key (str | None, optional): The name of the key to use for filtering. Defaults to None.
|
|
102
|
+
id_value (Any | None, optional): The value of the key to use for filtering. Defaults to None.
|
|
103
|
+
order_by (OrderByItem | None, optional): The order by item to use for sorting. Defaults to None.
|
|
96
104
|
offset (int, optional): The number of results to skip. Defaults to 0.
|
|
97
105
|
limit (int, optional): The maximum number of results to return. Defaults to 100.
|
|
98
|
-
|
|
106
|
+
custom_query (Any, optional): The custom query to use for the query. Defaults to None.
|
|
99
107
|
|
|
100
108
|
Returns:
|
|
101
109
|
Generator[DataModelType, None, None]: The result of the query.
|
|
102
110
|
"""
|
|
103
111
|
# Query and filter
|
|
104
112
|
_query = (
|
|
105
|
-
|
|
106
|
-
or
|
|
107
|
-
or self.
|
|
113
|
+
custom_query
|
|
114
|
+
or empty_data_class.query_base()
|
|
115
|
+
or self.filter_query(
|
|
116
|
+
empty_data_class.schema_name,
|
|
117
|
+
empty_data_class.table_name,
|
|
118
|
+
)
|
|
108
119
|
)
|
|
109
120
|
_params: tuple[Any, ...] = ()
|
|
110
121
|
_filter = None
|
|
111
122
|
|
|
112
123
|
# TODO: Rewrite this so that filter method with loop is not used here
|
|
113
|
-
if
|
|
114
|
-
(_filter, _params) = self.
|
|
124
|
+
if id_key and id_value:
|
|
125
|
+
(_filter, _params) = self.create_filter({id_key: id_value})
|
|
115
126
|
|
|
116
127
|
# Order and limit
|
|
117
|
-
_order = self.
|
|
118
|
-
_limit = self.
|
|
128
|
+
_order = self.order_query(order_by)
|
|
129
|
+
_limit = self.limit_query(offset, limit)
|
|
119
130
|
|
|
120
131
|
# Create a SQL object for the query and format it
|
|
121
|
-
|
|
132
|
+
query_sql = self._format_filter_query(_query, _filter, _order, _limit)
|
|
122
133
|
|
|
123
134
|
# Log
|
|
124
|
-
self.
|
|
135
|
+
self.log_query(self.db_cursor, query_sql, _params)
|
|
125
136
|
|
|
126
137
|
# Execute the query
|
|
127
|
-
self.
|
|
138
|
+
self.db_cursor.execute(query_sql, _params)
|
|
128
139
|
|
|
129
140
|
# Instead of fetchall(), we'll use a generator to yield results one by one
|
|
130
141
|
while True:
|
|
131
|
-
row = self.
|
|
142
|
+
row = self.db_cursor.fetchone()
|
|
132
143
|
if row is None:
|
|
133
144
|
break
|
|
134
145
|
|
|
135
|
-
yield self.
|
|
146
|
+
yield self.turn_data_into_model(empty_data_class.__class__, row)
|
|
136
147
|
|
|
137
|
-
def
|
|
148
|
+
def get_filtered(
|
|
138
149
|
self,
|
|
139
|
-
|
|
150
|
+
empty_data_class: DataModelType,
|
|
140
151
|
filter: dict[str, Any],
|
|
141
|
-
|
|
152
|
+
order_by: OrderByItem | None = None,
|
|
142
153
|
offset: int = 0,
|
|
143
154
|
limit: int = 100,
|
|
144
|
-
|
|
155
|
+
custom_query: Any = None,
|
|
145
156
|
) -> Generator[DataModelType, None, None]:
|
|
146
157
|
# Query and filter
|
|
147
158
|
_query = (
|
|
148
|
-
|
|
149
|
-
or
|
|
150
|
-
or self.
|
|
159
|
+
custom_query
|
|
160
|
+
or empty_data_class.query_base()
|
|
161
|
+
or self.filter_query(
|
|
162
|
+
empty_data_class.schema_name,
|
|
163
|
+
empty_data_class.table_name,
|
|
164
|
+
)
|
|
151
165
|
)
|
|
152
|
-
(_filter, _params) = self.
|
|
166
|
+
(_filter, _params) = self.create_filter(filter)
|
|
153
167
|
|
|
154
168
|
# Order and limit
|
|
155
|
-
_order = self.
|
|
156
|
-
_limit = self.
|
|
169
|
+
_order = self.order_query(order_by)
|
|
170
|
+
_limit = self.limit_query(offset, limit)
|
|
157
171
|
|
|
158
172
|
# Create SQL query
|
|
159
|
-
|
|
173
|
+
query_sql = self._format_filter_query(_query, _filter, _order, _limit)
|
|
160
174
|
|
|
161
175
|
# Log
|
|
162
|
-
self.
|
|
176
|
+
self.log_query(self.db_cursor, query_sql, _params)
|
|
163
177
|
|
|
164
178
|
# Execute the query
|
|
165
|
-
self.
|
|
179
|
+
self.db_cursor.execute(query_sql, _params)
|
|
166
180
|
|
|
167
181
|
# Instead of fetchall(), we'll use a generator to yield results one by one
|
|
168
182
|
while True:
|
|
169
|
-
row = self.
|
|
183
|
+
row = self.db_cursor.fetchone()
|
|
170
184
|
if row is None:
|
|
171
185
|
break
|
|
172
186
|
|
|
173
|
-
yield self.
|
|
187
|
+
yield self.turn_data_into_model(empty_data_class.__class__, row)
|
|
174
188
|
|
|
175
189
|
def _insert(
|
|
176
190
|
self,
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
191
|
+
empty_data_class: DBDataModel,
|
|
192
|
+
schema_name: str | None,
|
|
193
|
+
table_name: str,
|
|
194
|
+
store_data: dict[str, Any],
|
|
195
|
+
id_key: str,
|
|
182
196
|
) -> tuple[int, int]:
|
|
183
197
|
"""
|
|
184
198
|
Stores a record in the database.
|
|
185
199
|
|
|
186
200
|
Args:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
201
|
+
empty_data_class (DBDataModel): The data model to use for the query.
|
|
202
|
+
schema_name (str | None): The name of the schema to store the record in.
|
|
203
|
+
table_name (str): The name of the table to store the record in.
|
|
204
|
+
store_data (dict[str, Any]): The data to store.
|
|
205
|
+
id_key (str): The name of the key to use for the query.
|
|
192
206
|
|
|
193
207
|
Returns:
|
|
194
208
|
tuple[int, int]: The id of the record and the number of affected rows.
|
|
195
209
|
"""
|
|
196
|
-
values = list(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
210
|
+
values = list(store_data.values())
|
|
211
|
+
table_identifier = self.make_identifier(schema_name, table_name)
|
|
212
|
+
return_key = self.make_identifier(empty_data_class.table_alias, id_key)
|
|
213
|
+
insert_query = self._format_insert_query(
|
|
214
|
+
table_identifier,
|
|
215
|
+
store_data,
|
|
216
|
+
return_key,
|
|
217
|
+
)
|
|
200
218
|
|
|
201
219
|
# Log
|
|
202
|
-
self.
|
|
220
|
+
self.log_query(self.db_cursor, insert_query, tuple(values))
|
|
203
221
|
|
|
204
222
|
# Insert
|
|
205
|
-
self.
|
|
206
|
-
|
|
207
|
-
result = self.
|
|
223
|
+
self.db_cursor.execute(insert_query, tuple(values))
|
|
224
|
+
affected_rows = self.db_cursor.rowcount
|
|
225
|
+
result = self.db_cursor.fetchone()
|
|
208
226
|
|
|
209
227
|
return (
|
|
210
|
-
result[
|
|
211
|
-
|
|
228
|
+
result[id_key] if result and id_key in result else 0,
|
|
229
|
+
affected_rows,
|
|
212
230
|
)
|
|
213
231
|
|
|
214
232
|
@overload
|
|
@@ -235,71 +253,73 @@ class DBWrapper(DBWrapperMixin):
|
|
|
235
253
|
"""
|
|
236
254
|
status: list[tuple[int, int]] = []
|
|
237
255
|
|
|
238
|
-
|
|
256
|
+
one_record = False
|
|
239
257
|
if not isinstance(records, list):
|
|
240
|
-
|
|
258
|
+
one_record = True
|
|
241
259
|
records = [records]
|
|
242
260
|
|
|
243
261
|
for row in records:
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if not
|
|
262
|
+
store_id_key = row.id_key
|
|
263
|
+
store_data = row.store_data()
|
|
264
|
+
if not store_id_key or not store_data:
|
|
247
265
|
continue
|
|
248
266
|
|
|
249
267
|
res = self._insert(
|
|
250
268
|
row,
|
|
251
|
-
row.
|
|
252
|
-
row.
|
|
253
|
-
|
|
254
|
-
|
|
269
|
+
row.schema_name,
|
|
270
|
+
row.table_name,
|
|
271
|
+
store_data,
|
|
272
|
+
store_id_key,
|
|
255
273
|
)
|
|
256
274
|
if res:
|
|
257
275
|
row.id = res[0] # update the id of the row
|
|
258
276
|
|
|
259
277
|
status.append(res)
|
|
260
278
|
|
|
261
|
-
if
|
|
279
|
+
if one_record:
|
|
262
280
|
return status[0]
|
|
263
281
|
|
|
264
282
|
return status
|
|
265
283
|
|
|
266
284
|
def _update(
|
|
267
285
|
self,
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
286
|
+
empty_data_class: DBDataModel,
|
|
287
|
+
schema_name: str | None,
|
|
288
|
+
table_name: str,
|
|
289
|
+
update_data: dict[str, Any],
|
|
290
|
+
update_id: tuple[str, Any],
|
|
273
291
|
) -> int:
|
|
274
292
|
"""
|
|
275
293
|
Updates a record in the database.
|
|
276
294
|
|
|
277
295
|
Args:
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
296
|
+
empty_data_class (DBDataModel): The data model to use for the query.
|
|
297
|
+
schema_name (str | None): The name of the schema to update the record in.
|
|
298
|
+
table_name (str): The name of the table to update the record in.
|
|
299
|
+
update_data (dict[str, Any]): The data to update.
|
|
300
|
+
update_id (tuple[str, Any]): The id of the record to update.
|
|
283
301
|
|
|
284
302
|
Returns:
|
|
285
303
|
int: The number of affected rows.
|
|
286
304
|
"""
|
|
287
|
-
(
|
|
288
|
-
values = list(
|
|
289
|
-
values.append(
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
305
|
+
(id_key, id_value) = update_id
|
|
306
|
+
values = list(update_data.values())
|
|
307
|
+
values.append(id_value)
|
|
308
|
+
|
|
309
|
+
table_identifier = self.make_identifier(schema_name, table_name)
|
|
310
|
+
update_key = self.make_identifier(empty_data_class.table_alias, id_key)
|
|
311
|
+
update_query = self._format_update_query(
|
|
312
|
+
table_identifier, update_key, update_data
|
|
313
|
+
)
|
|
294
314
|
|
|
295
315
|
# Log
|
|
296
|
-
self.
|
|
316
|
+
self.log_query(self.db_cursor, update_query, tuple(values))
|
|
297
317
|
|
|
298
318
|
# Update
|
|
299
|
-
self.
|
|
300
|
-
|
|
319
|
+
self.db_cursor.execute(update_query, tuple(values))
|
|
320
|
+
affected_rows = self.db_cursor.rowcount
|
|
301
321
|
|
|
302
|
-
return
|
|
322
|
+
return affected_rows
|
|
303
323
|
|
|
304
324
|
@overload
|
|
305
325
|
def update(self, records: DataModelType) -> int: # type: ignore
|
|
@@ -321,53 +341,53 @@ class DBWrapper(DBWrapperMixin):
|
|
|
321
341
|
"""
|
|
322
342
|
status: list[int] = []
|
|
323
343
|
|
|
324
|
-
|
|
344
|
+
one_record = False
|
|
325
345
|
if not isinstance(records, list):
|
|
326
|
-
|
|
346
|
+
one_record = True
|
|
327
347
|
records = [records]
|
|
328
348
|
|
|
329
349
|
for row in records:
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
if not
|
|
350
|
+
update_data = row.update_data()
|
|
351
|
+
update_id_key = row.id_key
|
|
352
|
+
update_id_value = row.id
|
|
353
|
+
if not update_data or not update_id_key or not update_id_value:
|
|
334
354
|
continue
|
|
335
355
|
|
|
336
356
|
status.append(
|
|
337
357
|
self._update(
|
|
338
358
|
row,
|
|
339
|
-
row.
|
|
340
|
-
row.
|
|
341
|
-
|
|
359
|
+
row.schema_name,
|
|
360
|
+
row.table_name,
|
|
361
|
+
update_data,
|
|
342
362
|
(
|
|
343
|
-
|
|
344
|
-
|
|
363
|
+
update_id_key,
|
|
364
|
+
update_id_value,
|
|
345
365
|
),
|
|
346
366
|
)
|
|
347
367
|
)
|
|
348
368
|
|
|
349
|
-
if
|
|
369
|
+
if one_record:
|
|
350
370
|
return status[0]
|
|
351
371
|
|
|
352
372
|
return status
|
|
353
373
|
|
|
354
|
-
def
|
|
374
|
+
def update_data(
|
|
355
375
|
self,
|
|
356
376
|
record: DBDataModel,
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
377
|
+
update_data: dict[str, Any],
|
|
378
|
+
update_id_key: str | None = None,
|
|
379
|
+
update_id_value: Any = None,
|
|
360
380
|
) -> int:
|
|
361
|
-
|
|
362
|
-
|
|
381
|
+
update_id_key = update_id_key or record.id_key
|
|
382
|
+
update_id_value = update_id_value or record.id
|
|
363
383
|
status = self._update(
|
|
364
384
|
record,
|
|
365
|
-
record.
|
|
366
|
-
record.
|
|
367
|
-
|
|
385
|
+
record.schema_name,
|
|
386
|
+
record.table_name,
|
|
387
|
+
update_data,
|
|
368
388
|
(
|
|
369
|
-
|
|
370
|
-
|
|
389
|
+
update_id_key,
|
|
390
|
+
update_id_value,
|
|
371
391
|
),
|
|
372
392
|
)
|
|
373
393
|
|
|
@@ -375,35 +395,35 @@ class DBWrapper(DBWrapperMixin):
|
|
|
375
395
|
|
|
376
396
|
def _delete(
|
|
377
397
|
self,
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
398
|
+
empty_data_class: DBDataModel,
|
|
399
|
+
schema_name: str | None,
|
|
400
|
+
table_name: str,
|
|
401
|
+
delete_id: tuple[str, Any],
|
|
382
402
|
) -> int:
|
|
383
403
|
"""
|
|
384
404
|
Deletes a record from the database.
|
|
385
405
|
|
|
386
406
|
Args:
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
407
|
+
empty_data_class (DBDataModel): The data model to use for the query.
|
|
408
|
+
schema_name (str | None): The name of the schema to delete the record from.
|
|
409
|
+
table_name (str): The name of the table to delete the record from.
|
|
410
|
+
delete_id (tuple[str, Any]): The id of the record to delete.
|
|
391
411
|
|
|
392
412
|
Returns:
|
|
393
413
|
int: The number of affected rows.
|
|
394
414
|
"""
|
|
395
|
-
(
|
|
415
|
+
(id_key, id_value) = delete_id
|
|
396
416
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
delete_query = self.
|
|
417
|
+
table_identifier = self.make_identifier(schema_name, table_name)
|
|
418
|
+
delete_key = self.make_identifier(empty_data_class.table_alias, id_key)
|
|
419
|
+
delete_query = self._format_delete_query(table_identifier, delete_key)
|
|
400
420
|
|
|
401
421
|
# Log
|
|
402
|
-
self.
|
|
422
|
+
self.log_query(self.db_cursor, delete_query, (id_value,))
|
|
403
423
|
|
|
404
424
|
# Delete
|
|
405
|
-
self.
|
|
406
|
-
affected_rows = self.
|
|
425
|
+
self.db_cursor.execute(delete_query, (id_value,))
|
|
426
|
+
affected_rows = self.db_cursor.rowcount
|
|
407
427
|
|
|
408
428
|
return affected_rows
|
|
409
429
|
|
|
@@ -427,30 +447,30 @@ class DBWrapper(DBWrapperMixin):
|
|
|
427
447
|
"""
|
|
428
448
|
status: list[int] = []
|
|
429
449
|
|
|
430
|
-
|
|
450
|
+
one_record = False
|
|
431
451
|
if not isinstance(records, list):
|
|
432
|
-
|
|
452
|
+
one_record = True
|
|
433
453
|
records = [records]
|
|
434
454
|
|
|
435
455
|
for row in records:
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
if not
|
|
456
|
+
delete_id_key = row.id_key
|
|
457
|
+
delete_id_value = row.id
|
|
458
|
+
if not delete_id_key or not delete_id_value:
|
|
439
459
|
continue
|
|
440
460
|
|
|
441
461
|
status.append(
|
|
442
462
|
self._delete(
|
|
443
463
|
row,
|
|
444
|
-
row.
|
|
445
|
-
row.
|
|
464
|
+
row.schema_name,
|
|
465
|
+
row.table_name,
|
|
446
466
|
(
|
|
447
|
-
|
|
448
|
-
|
|
467
|
+
delete_id_key,
|
|
468
|
+
delete_id_value,
|
|
449
469
|
),
|
|
450
470
|
)
|
|
451
471
|
)
|
|
452
472
|
|
|
453
|
-
if
|
|
473
|
+
if one_record:
|
|
454
474
|
return status[0]
|
|
455
475
|
|
|
456
476
|
return status
|