Skip to content

Commit 3e647d1

Browse files
authored
Merge pull request #4 from flask-pro/add-validate-decorator
add `Validate` decorators.
2 parents 094d2ee + c3a0625 commit 3e647d1

File tree

18 files changed

+424
-418
lines changed

18 files changed

+424
-418
lines changed

.github/workflows/testing.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ jobs:
2626
3.10
2727
3.11
2828
3.12
29+
3.13
2930
- run: make test
3031
tox:
3132
runs-on: ubuntu-latest
@@ -38,6 +39,7 @@ jobs:
3839
3.10
3940
3.11
4041
3.12
42+
3.13
4143
- run: make tox
4244
build:
4345
runs-on: ubuntu-latest

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## Version 4.0.0
2+
3+
* Add `Validation` decorators.
4+
15
## Version 3.0.0
26

37
* Add method `paginate()` to `ReadMixin`.

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ build: clean venv
3838

3939
install: build
4040
$(PIP) install dist/db_first-*.tar.gz
41+
$(PRE_COMMIT) install
4142

4243
upload_to_testpypi: build
4344
$(PYTHON_VENV) -m twine upload --repository-url https://test.pypi.org/legacy/ dist/*

README.md

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,18 @@ $ pip install -U db_first
3535
### Full example
3636

3737
```python
38-
from uuid import UUID
39-
4038
from db_first import BaseCRUD
4139
from db_first.base_model import ModelMixin
40+
from db_first.decorators import Validation
4241
from db_first.mixins import CreateMixin
4342
from db_first.mixins import DeleteMixin
4443
from db_first.mixins import ReadMixin
4544
from db_first.mixins import UpdateMixin
4645
from marshmallow import fields
4746
from marshmallow import Schema
4847
from sqlalchemy import create_engine
48+
from sqlalchemy import Result
49+
from sqlalchemy.exc import NoResultFound
4950
from sqlalchemy.orm import declarative_base
5051
from sqlalchemy.orm import Mapped
5152
from sqlalchemy.orm import mapped_column
@@ -64,60 +65,63 @@ class Items(ModelMixin, Base):
6465
Base.metadata.create_all(engine)
6566

6667

67-
class InputSchemaOfCreate(Schema):
68-
data = fields.String()
68+
class IdSchema(Schema):
69+
id = fields.UUID()
6970

7071

71-
class InputSchemaOfUpdate(InputSchemaOfCreate):
72-
id = fields.UUID()
72+
class SchemaOfCreate(Schema):
73+
data = fields.String()
7374

7475

75-
class InputSchemaOfRead(Schema):
76-
id = fields.UUID()
76+
class SchemaOfUpdate(IdSchema, SchemaOfCreate):
77+
"""Update item schema."""
7778

7879

79-
class OutputSchema(InputSchemaOfUpdate):
80+
class OutputSchema(SchemaOfUpdate):
8081
created_at = fields.DateTime()
8182

8283

8384
class ItemController(CreateMixin, ReadMixin, UpdateMixin, DeleteMixin, BaseCRUD):
8485
class Meta:
8586
session = session
8687
model = Items
87-
input_schema_of_create = InputSchemaOfCreate
88-
input_schema_of_update = InputSchemaOfUpdate
89-
output_schema_of_create = OutputSchema
90-
input_schema_of_read = InputSchemaOfRead
91-
output_schema_of_read = OutputSchema
92-
output_schema_of_update = OutputSchema
93-
schema_of_paginate = OutputSchema
9488
sortable = ['created_at']
9589

90+
@Validation.input(SchemaOfCreate)
91+
@Validation.output(OutputSchema, serialize=True)
92+
def create(self, **data) -> Result:
93+
return super().create_object(**data)
9694

97-
if __name__ == '__main__':
98-
item = ItemController()
95+
@Validation.input(IdSchema, keys=['id'])
96+
@Validation.output(OutputSchema, serialize=True)
97+
def read(self, **data) -> Result:
98+
return super().read_object(data['id'])
99+
100+
@Validation.input(SchemaOfUpdate)
101+
@Validation.output(OutputSchema, serialize=True)
102+
def update(self, **data) -> Result:
103+
return super().update_object(**data)
99104

100-
first_new_item = item.create({'data': 'first'}, deserialize=True)
101-
print('Item as object:', first_new_item)
102-
second_new_item = item.create({'data': 'second'}, deserialize=True, serialize=True)
103-
print('Item as dict:', second_new_item)
105+
@Validation.input(IdSchema, keys=['id'])
106+
def delete(self, **data) -> None:
107+
super().delete_object(**data)
104108

105-
first_item = item.read({'id': first_new_item.id})
106-
print('Item as object:', first_item)
107-
first_item = item.read({'id': first_new_item.id})
108-
print('Item as dict:', first_item)
109109

110-
updated_first_item = item.update(data={'id': first_new_item.id, 'data': 'updated_first'})
111-
print('Item as object:', updated_first_item)
112-
updated_second_item = item.update(
113-
data={'id': UUID(second_new_item['id']), 'data': 'updated_second'}, serialize=True
114-
)
115-
print('Item as dict:', updated_second_item)
110+
if __name__ == '__main__':
111+
item_controller = ItemController()
112+
113+
new_item = item_controller.create(data='first')
114+
print('Item as dict:', new_item)
116115

117-
items = item.paginate(sort_created_at='desc')
118-
print('Items as objects:', items)
119-
items = item.paginate(sort_created_at='desc', serialize=True)
120-
print('Items as dicts:', items)
116+
item = item_controller.read(id=new_item['id'])
117+
print('Item as dict:', item)
121118

119+
updated_item = item_controller.update(id=new_item['id'], data='updated_first')
120+
print('Item as dict:', updated_item)
122121

122+
item_controller.delete(id=new_item['id'])
123+
try:
124+
item = item_controller.read(id=new_item['id'])
125+
except NoResultFound:
126+
print('Item deleted:', item)
123127
```

examples/full_example.py

Lines changed: 45 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
from uuid import UUID
2-
31
from db_first import BaseCRUD
42
from db_first.base_model import ModelMixin
3+
from db_first.decorators import Validation
54
from db_first.mixins import CreateMixin
65
from db_first.mixins import DeleteMixin
76
from db_first.mixins import ReadMixin
87
from db_first.mixins import UpdateMixin
98
from marshmallow import fields
109
from marshmallow import Schema
1110
from sqlalchemy import create_engine
11+
from sqlalchemy import Result
12+
from sqlalchemy.exc import NoResultFound
1213
from sqlalchemy.orm import declarative_base
1314
from sqlalchemy.orm import Mapped
1415
from sqlalchemy.orm import mapped_column
@@ -27,57 +28,62 @@ class Items(ModelMixin, Base):
2728
Base.metadata.create_all(engine)
2829

2930

30-
class InputSchemaOfCreate(Schema):
31-
data = fields.String()
31+
class IdSchema(Schema):
32+
id = fields.UUID()
3233

3334

34-
class InputSchemaOfUpdate(InputSchemaOfCreate):
35-
id = fields.UUID()
35+
class SchemaOfCreate(Schema):
36+
data = fields.String()
3637

3738

38-
class InputSchemaOfRead(Schema):
39-
id = fields.UUID()
39+
class SchemaOfUpdate(IdSchema, SchemaOfCreate):
40+
"""Update item schema."""
4041

4142

42-
class OutputSchema(InputSchemaOfUpdate):
43+
class OutputSchema(SchemaOfUpdate):
4344
created_at = fields.DateTime()
4445

4546

4647
class ItemController(CreateMixin, ReadMixin, UpdateMixin, DeleteMixin, BaseCRUD):
4748
class Meta:
4849
session = session
4950
model = Items
50-
input_schema_of_create = InputSchemaOfCreate
51-
input_schema_of_update = InputSchemaOfUpdate
52-
output_schema_of_create = OutputSchema
53-
input_schema_of_read = InputSchemaOfRead
54-
output_schema_of_read = OutputSchema
55-
output_schema_of_update = OutputSchema
56-
schema_of_paginate = OutputSchema
5751
sortable = ['created_at']
5852

53+
@Validation.input(SchemaOfCreate)
54+
@Validation.output(OutputSchema, serialize=True)
55+
def create(self, **data) -> Result:
56+
return super().create_object(**data)
57+
58+
@Validation.input(IdSchema, keys=['id'])
59+
@Validation.output(OutputSchema, serialize=True)
60+
def read(self, **data) -> Result:
61+
return super().read_object(data['id'])
62+
63+
@Validation.input(SchemaOfUpdate)
64+
@Validation.output(OutputSchema, serialize=True)
65+
def update(self, **data) -> Result:
66+
return super().update_object(**data)
67+
68+
@Validation.input(IdSchema, keys=['id'])
69+
def delete(self, **data) -> None:
70+
super().delete_object(**data)
71+
5972

6073
if __name__ == '__main__':
61-
item = ItemController()
62-
63-
first_new_item = item.create({'data': 'first'}, deserialize=True)
64-
print('Item as object:', first_new_item)
65-
second_new_item = item.create({'data': 'second'}, deserialize=True, serialize=True)
66-
print('Item as dict:', second_new_item)
67-
68-
first_item = item.read({'id': first_new_item.id})
69-
print('Item as object:', first_item)
70-
first_item = item.read({'id': first_new_item.id})
71-
print('Item as dict:', first_item)
72-
73-
updated_first_item = item.update(data={'id': first_new_item.id, 'data': 'updated_first'})
74-
print('Item as object:', updated_first_item)
75-
updated_second_item = item.update(
76-
data={'id': UUID(second_new_item['id']), 'data': 'updated_second'}, serialize=True
77-
)
78-
print('Item as dict:', updated_second_item)
79-
80-
items = item.paginate(sort_created_at='desc')
81-
print('Items as objects:', items)
82-
items = item.paginate(sort_created_at='desc', serialize=True)
83-
print('Items as dicts:', items)
74+
item_controller = ItemController()
75+
76+
new_item = item_controller.create(data='first')
77+
print('Item as dict:', new_item)
78+
79+
item = item_controller.read(id=new_item['id'])
80+
print('Item as dict:', item)
81+
82+
updated_item = item_controller.update(id=new_item['id'], data='updated_first')
83+
print('Item as dict:', updated_item)
84+
85+
item_controller.delete(id=new_item['id'])
86+
try:
87+
item = item_controller.read(id=new_item['id'])
88+
except NoResultFound:
89+
print('Item deleted:', item)

pyproject.toml

Lines changed: 2 additions & 2 deletions
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 = "3.0.0"
28+
version = "4.0.0"
2929

3030
[project.optional-dependencies]
3131
dev = [
@@ -68,7 +68,7 @@ check-overridden = true
6868
check-property-returns = true
6969
check-protected = true
7070
check-protected-class-methods = true
71-
disable = ["SIG101"]
71+
disable = ["SIG101", "SIG501"]
7272

7373
[tool.setuptools.packages.find]
7474
include = ["db_first*"]

src/db_first/base.py

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
from typing import Any
22
from typing import Optional
33

4-
from sqlalchemy.engine import Result
5-
64
from .exc import MetaNotFound
75
from .exc import OptionNotFound
86

@@ -24,42 +22,3 @@ def _get_option_from_meta(cls, name: str, default: Optional[Any] = ...) -> Any:
2422
option = default
2523

2624
return option
27-
28-
@classmethod
29-
def deserialize_data(cls, schema_name: str, data: dict) -> dict:
30-
schema = cls._get_option_from_meta(schema_name)
31-
return schema().load(data)
32-
33-
@classmethod
34-
def _clean_data(cls, data: Any) -> Any:
35-
"""Clearing hierarchical structures from empty values.
36-
37-
Cleaning occurs for objects of the list and dict types, other types do not clean.
38-
39-
:param data: an object for cleaning.
40-
:return: cleaned object.
41-
"""
42-
43-
empty_values = ('', None, ..., [], {}, (), set())
44-
45-
if isinstance(data, dict):
46-
cleaned_dict = {k: cls._clean_data(v) for k, v in data.items()}
47-
return {k: v for k, v in cleaned_dict.items() if v not in empty_values}
48-
49-
elif isinstance(data, list):
50-
cleaned_list = [cls._clean_data(item) for item in data]
51-
return [item for item in cleaned_list if item not in empty_values]
52-
53-
else:
54-
return data
55-
56-
@classmethod
57-
def serialize_data(cls, schema_name: str, data: Result, fields: list = None) -> dict:
58-
output_schema = cls._get_option_from_meta(schema_name)
59-
60-
if isinstance(data, list):
61-
serialized_data = output_schema(many=True, only=fields).dump(data)
62-
else:
63-
serialized_data = output_schema(only=fields).dump(data)
64-
65-
return cls._clean_data(serialized_data)

src/db_first/base_model.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import uuid
22
from datetime import datetime
33
from typing import Optional
4-
from uuid import UUID
54

65
from sqlalchemy.orm import Mapped
76
from sqlalchemy.orm import mapped_column
87

98

10-
def make_uuid4() -> UUID:
9+
def make_uuid4() -> uuid.UUID:
1110
return uuid.uuid4()
1211

1312

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .validation import Validation
2+
3+
__all__ = ['Validation']

0 commit comments

Comments
 (0)