Skip to content

Commit ccd2f7d

Browse files
authored
Merge pull request #1 from flask-pro/refactoring-basecrud-class
improve interfaces.
2 parents 5cb0fd6 + 3f4eddd commit ccd2f7d

File tree

11 files changed

+163
-79
lines changed

11 files changed

+163
-79
lines changed

.pre-commit-config.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,5 +86,3 @@ repos:
8686
- id: bandit
8787
args: [-c, pyproject.toml]
8888
additional_dependencies: ['bandit[toml]']
89-
default_language_version:
90-
python: python3.9

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## Version 2.0.0
2+
3+
* Refactoring of the module has been carried out. Improved class and method interfaces.
4+
15
## Version 1.0.0
26

37
* Initial public release.

README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ CRUD tools for working with database via SQLAlchemy.
77
- [DB-First](#db-first)
88
- [Features](#features)
99
- [Installation](#installation)
10-
- [Example](#example)
10+
- [Examples](#examples)
11+
- [Full example](#full-example)
1112

1213
<!--TOC-->
1314

@@ -29,9 +30,9 @@ Install and update using `pip`:
2930
$ pip install -U db_first
3031
```
3132

32-
## Example
33+
## Examples
3334

34-
File with application initialization `main.py`:
35+
### Full example
3536

3637
```python
3738
from uuid import UUID
@@ -85,7 +86,7 @@ class ItemController(CreateMixin, ReadMixin, UpdateMixin, DeleteMixin, Paginatio
8586
output_schema_of_create = OutputSchema
8687
output_schema_of_read = OutputSchema
8788
output_schema_of_update = OutputSchema
88-
output_schema_of_paginate = OutputSchema
89+
schema_of_paginate = OutputSchema
8990
sortable = ['created_at']
9091

9192

@@ -94,24 +95,23 @@ if __name__ == '__main__':
9495

9596
first_new_item = item.create(data={'data': 'first'})
9697
print('Item as object:', first_new_item)
97-
second_new_item = item.create(data={'data': 'second'}, jsonify=True)
98+
second_new_item = item.create(data={'data': 'second'}, serialize=True)
9899
print('Item as dict:', second_new_item)
99100

100101
first_item = item.read(first_new_item.id)
101102
print('Item as object:', first_item)
102-
first_item = item.read(first_new_item.id, jsonify=True)
103+
first_item = item.read(first_new_item.id, serialize=True)
103104
print('Item as dict:', first_item)
104105

105106
updated_first_item = item.update(data={'id': first_new_item.id, 'data': 'updated_first'})
106107
print('Item as object:', updated_first_item)
107108
updated_second_item = item.update(
108-
data={'id': UUID(second_new_item['id']), 'data': 'updated_second'}, jsonify=True
109+
data={'id': UUID(second_new_item['id']), 'data': 'updated_second'}, serialize=True
109110
)
110111
print('Item as dict:', updated_second_item)
111112

112113
items = item.paginate(sort_created_at='desc')
113114
print('Items as objects:', items)
114-
items = item.paginate(sort_created_at='desc', jsonify=True)
115+
items = item.paginate(sort_created_at='desc', serialize=True)
115116
print('Items as dicts:', items)
116-
117117
```

examples/full_example.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class Meta:
4949
output_schema_of_create = OutputSchema
5050
output_schema_of_read = OutputSchema
5151
output_schema_of_update = OutputSchema
52-
output_schema_of_paginate = OutputSchema
52+
schema_of_paginate = OutputSchema
5353
sortable = ['created_at']
5454

5555

@@ -58,22 +58,22 @@ class Meta:
5858

5959
first_new_item = item.create(data={'data': 'first'})
6060
print('Item as object:', first_new_item)
61-
second_new_item = item.create(data={'data': 'second'}, jsonify=True)
61+
second_new_item = item.create(data={'data': 'second'}, serialize=True)
6262
print('Item as dict:', second_new_item)
6363

6464
first_item = item.read(first_new_item.id)
6565
print('Item as object:', first_item)
66-
first_item = item.read(first_new_item.id, jsonify=True)
66+
first_item = item.read(first_new_item.id, serialize=True)
6767
print('Item as dict:', first_item)
6868

6969
updated_first_item = item.update(data={'id': first_new_item.id, 'data': 'updated_first'})
7070
print('Item as object:', updated_first_item)
7171
updated_second_item = item.update(
72-
data={'id': UUID(second_new_item['id']), 'data': 'updated_second'}, jsonify=True
72+
data={'id': UUID(second_new_item['id']), 'data': 'updated_second'}, serialize=True
7373
)
7474
print('Item as dict:', updated_second_item)
7575

7676
items = item.paginate(sort_created_at='desc')
7777
print('Items as objects:', items)
78-
items = item.paginate(sort_created_at='desc', jsonify=True)
78+
items = item.paginate(sort_created_at='desc', serialize=True)
7979
print('Items as dicts:', items)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ license = {file = "LICENSE"}
2525
name = "DB-First"
2626
readme = "README.md"
2727
requires-python = ">=3.9"
28-
version = "1.0.0"
28+
version = "2.0.0"
2929

3030
[project.optional-dependencies]
3131
dev = [

src/db_first/base.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from typing import Any
2-
from typing import Optional
32

43
from sqlalchemy.engine import Result
54

@@ -9,24 +8,22 @@
98

109
class BaseCRUD:
1110
@classmethod
12-
def _get_option_from_meta(cls, name: str, default: Any = ...) -> Optional[Any]:
13-
meta = getattr(cls, 'Meta', None)
14-
if meta is None:
11+
def _get_option_from_meta(cls, name: str) -> Any:
12+
try:
13+
meta = cls.Meta
14+
except AttributeError:
1515
raise MetaNotFound('You need add class Meta with options.')
1616

1717
try:
1818
option = getattr(meta, name)
1919
except AttributeError:
20-
if default is Ellipsis:
21-
raise OptionNotFound(f'Option <{name}> not set in Meta class.')
22-
else:
23-
option = default
20+
raise OptionNotFound(f'Option <{name}> not set in Meta class.')
2421

2522
return option
2623

2724
@classmethod
28-
def _deserialize_data(cls, schema_name: str, data: dict) -> dict:
29-
schema = cls._get_option_from_meta(schema_name, None)
25+
def deserialize_data(cls, schema_name: str, data: dict) -> dict:
26+
schema = cls._get_option_from_meta(schema_name)
3027
return schema().load(data)
3128

3229
@classmethod
@@ -39,7 +36,7 @@ def _clean_data(cls, data: Any) -> Any:
3936
:return: cleaned object.
4037
"""
4138

42-
empty_values = ['', None, [], {}, (), set()]
39+
empty_values = ('', None, [], {}, (), set())
4340

4441
if isinstance(data, dict):
4542
cleaned_dict = {k: cls._clean_data(v) for k, v in data.items()}
@@ -53,8 +50,8 @@ def _clean_data(cls, data: Any) -> Any:
5350
return data
5451

5552
@classmethod
56-
def _data_to_json(cls, schema_name: str, data: Result, fields: list = None) -> dict:
57-
output_schema = cls._get_option_from_meta(schema_name, None)
53+
def serialize_data(cls, schema_name: str, data: Result, fields: list = None) -> dict:
54+
output_schema = cls._get_option_from_meta(schema_name)
5855

5956
if isinstance(data, list):
6057
serialized_data = output_schema(many=True, only=fields).dump(data)

src/db_first/mixins/crud.py

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,32 @@
22

33
from sqlalchemy import delete
44
from sqlalchemy import select
5+
from sqlalchemy import update
56
from sqlalchemy.engine import Result
67

78

89
class CreateMixin:
9-
"""Create object in database."""
10+
"""Create object in database.
11+
12+
This mixin supports the following options in the Meta class:
13+
```
14+
class CustomController(CreateMixin, BaseCRUD):
15+
class Meta:
16+
session = Session
17+
model = Model
18+
input_schema_of_create = InputSchema
19+
output_schema_of_create = OutputSchema
20+
21+
custom_controller = CustomController()
22+
```
23+
24+
`input_schema_of_create` - marshmallow schema for validating and deserialization input data.
25+
`output_schema_of_create` - marshmallow schema for serialization output data.
26+
"""
27+
28+
def create_object(self, **data) -> Result:
29+
"""If this method does not suit you, simply override it in your class."""
1030

11-
def _create_object(self, **data) -> Result:
1231
session = self._get_option_from_meta('session')
1332
model = self._get_option_from_meta('model')
1433

@@ -17,69 +36,70 @@ def _create_object(self, **data) -> Result:
1736
session.commit()
1837
return new_obj
1938

20-
def create(self, data: dict, validating: bool = True, jsonify: bool = False) -> Result or dict:
21-
if validating:
22-
self._deserialize_data('input_schema_of_create', data)
23-
24-
new_object = self._create_object(**data)
39+
def create(self, data: dict, serialize: bool = False) -> Result or dict:
40+
deserialized_data = self.deserialize_data('input_schema_of_create', data)
41+
new_object = self.create_object(**deserialized_data)
2542

26-
if jsonify:
27-
return self._data_to_json('output_schema_of_create', new_object)
43+
if serialize:
44+
return self.serialize_data('output_schema_of_create', new_object)
2845

2946
return new_object
3047

3148

3249
class ReadMixin:
3350
"""Read object from database."""
3451

35-
def _read_object(self, id: Any) -> Result:
52+
def get_object(self, id: Any) -> Result:
53+
"""If this method does not suit you, simply override it in your class."""
54+
3655
session = self._get_option_from_meta('session')
3756
model = self._get_option_from_meta('model')
3857
return session.scalars(select(model).where(model.id == id)).one()
3958

40-
def read(self, id, jsonify: bool = False) -> Result or dict:
41-
obj = self._read_object(id)
59+
def read(self, id, serialize: bool = False) -> Result or dict:
60+
obj = self.get_object(id)
4261

43-
if jsonify:
44-
return self._data_to_json('output_schema_of_read', obj)
62+
if serialize:
63+
return self.serialize_data('output_schema_of_read', obj)
4564

4665
return obj
4766

4867

4968
class UpdateMixin:
5069
"""Update object in database."""
5170

52-
def _update_object(self, id: Any, **data) -> Result:
71+
def update_object(self, id: Any, **data) -> Result:
72+
"""If this method does not suit you, simply override it in your class."""
73+
5374
session = self._get_option_from_meta('session')
5475
model = self._get_option_from_meta('model')
5576

77+
stmt = update(model).where(model.id == id).values(**data)
78+
session.execute(stmt)
79+
5680
obj = session.scalars(select(model).where(model.id == id)).one()
57-
for k, v in data.items():
58-
setattr(obj, k, v)
59-
session.commit()
6081
return obj
6182

62-
def update(self, data: dict, validating: bool = True, jsonify: bool = False) -> Result or dict:
63-
if validating:
64-
self._deserialize_data('input_schema_of_update', data)
83+
def update(self, data: dict, serialize: bool = False) -> Result or dict:
84+
deserialized_data = self.deserialize_data('input_schema_of_update', data)
85+
updated_object = self.update_object(**deserialized_data)
6586

66-
updated_object = self._update_object(**data)
67-
68-
if jsonify:
69-
return self._data_to_json('output_schema_of_update', updated_object)
87+
if serialize:
88+
return self.serialize_data('output_schema_of_update', updated_object)
7089

7190
return updated_object
7291

7392

7493
class DeleteMixin:
7594
"""Delete object from database."""
7695

77-
def _delete_object(self, id: Any) -> None:
96+
def delete_object(self, id: Any) -> None:
97+
"""If this method does not suit you, simply override it in your class."""
98+
7899
session = self._get_option_from_meta('session')
79100
model = self._get_option_from_meta('model')
80101

81102
session.execute(delete(model).where(model.id == id))
82-
session.commit()
83103

84104
def delete(self, id: Any) -> None:
85-
self._delete_object(id)
105+
self.delete_object(id)

0 commit comments

Comments
 (0)