fix for obsolete pydantic parameters

This commit is contained in:
collerek
2021-08-06 16:03:29 +02:00
parent 25adb8378e
commit 521b9e6c12
10 changed files with 89 additions and 34 deletions

View File

@ -14,23 +14,19 @@ Each of the `Fields` has assigned both `sqlalchemy` column class and python type
### String ### String
`String(max_length, `String(max_length: int,
allow_blank: bool = True,
strip_whitespace: bool = False,
min_length: int = None, min_length: int = None,
max_length: int = None,
curtail_length: int = None,
regex: str = None,)` has a required `max_length` parameter. regex: str = None,)` has a required `max_length` parameter.
* Sqlalchemy column: `sqlalchemy.String` * Sqlalchemy column: `sqlalchemy.String`
* Type (used for pydantic): `str` * Type (used for pydantic): `str`
!!!tip !!!tip
For explanation of other parameters check [pydantic][pydantic] documentation. For explanation of other parameters check [pydantic](https://pydantic-docs.helpmanual.io/usage/schema/#field-customisation) documentation.
### Text ### Text
`Text(allow_blank: bool = True, strip_whitespace: bool = False)` has no required parameters. `Text()` has no required parameters.
* Sqlalchemy column: `sqlalchemy.Text` * Sqlalchemy column: `sqlalchemy.Text`
* Type (used for pydantic): `str` * Type (used for pydantic): `str`
@ -247,5 +243,5 @@ response = client.post(
[relations]: ../relations/index.md [relations]: ../relations/index.md
[queries]: ../queries.md [queries]: ../queries.md
[pydantic]: https://pydantic-docs.helpmanual.io/usage/types/#constrained-types [pydantic]: https://pydantic-docs.helpmanual.io/usage/schema/#field-customisation
[server default]: https://docs.sqlalchemy.org/en/13/core/defaults.html#server-invoked-ddl-explicit-default-expressions [server default]: https://docs.sqlalchemy.org/en/13/core/defaults.html#server-invoked-ddl-explicit-default-expressions

View File

@ -1,3 +1,18 @@
# 0.10.16
## ✨ Features
* Allow passing your own pydantic `Config` to `ormar.Model` that will be merged with the default one by @naturalethic (thanks!) [#285](https://github.com/collerek/ormar/issues/285)
* Add `SmallInteger` field type by @ProgrammerPlus1998 (thanks!) [#297](https://github.com/collerek/ormar/pull/297)
## 🐛 Fixes
* Fix generating openapi schema by removing obsolete pydantic field parameters that were directly exposed in schema [#291](https://github.com/collerek/ormar/issues/291)
* Fix unnecessary warning for auto generated through models [#295](https://github.com/collerek/ormar/issues/295)
# 0.10.15 # 0.10.15
## 🐛 Fixes ## 🐛 Fixes

View File

@ -41,7 +41,6 @@ from ormar.exceptions import ( # noqa: I100
from ormar.fields import ( from ormar.fields import (
BaseField, BaseField,
BigInteger, BigInteger,
SmallInteger,
Boolean, Boolean,
DECODERS_MAP, DECODERS_MAP,
Date, Date,
@ -57,6 +56,7 @@ from ormar.fields import (
LargeBinary, LargeBinary,
ManyToMany, ManyToMany,
ManyToManyField, ManyToManyField,
SmallInteger,
String, String,
Text, Text,
Time, Time,
@ -77,7 +77,7 @@ class UndefinedType: # pragma no cover
Undefined = UndefinedType() Undefined = UndefinedType()
__version__ = "0.10.15" __version__ = "0.10.16"
__all__ = [ __all__ = [
"Integer", "Integer",
"BigInteger", "BigInteger",

View File

@ -9,7 +9,6 @@ from ormar.fields.foreign_key import ForeignKey, ForeignKeyField, UniqueColumns
from ormar.fields.many_to_many import ManyToMany, ManyToManyField from ormar.fields.many_to_many import ManyToMany, ManyToManyField
from ormar.fields.model_fields import ( from ormar.fields.model_fields import (
BigInteger, BigInteger,
SmallInteger,
Boolean, Boolean,
Date, Date,
DateTime, DateTime,
@ -18,6 +17,7 @@ from ormar.fields.model_fields import (
Integer, Integer,
JSON, JSON,
LargeBinary, LargeBinary,
SmallInteger,
String, String,
Text, Text,
Time, Time,

View File

@ -142,10 +142,7 @@ class String(ModelFieldFactory, str):
cls, cls,
*, *,
max_length: int, max_length: int,
allow_blank: bool = True,
strip_whitespace: bool = False,
min_length: int = None, min_length: int = None,
curtail_length: int = None,
regex: str = None, regex: str = None,
**kwargs: Any **kwargs: Any
) -> BaseField: # type: ignore ) -> BaseField: # type: ignore
@ -157,7 +154,6 @@ class String(ModelFieldFactory, str):
if k not in ["cls", "__class__", "kwargs"] if k not in ["cls", "__class__", "kwargs"]
}, },
} }
kwargs["allow_blank"] = kwargs.get("nullable", True)
return super().__new__(cls, **kwargs) return super().__new__(cls, **kwargs)
@classmethod @classmethod
@ -244,7 +240,7 @@ class Text(ModelFieldFactory, str):
_sample = "text" _sample = "text"
def __new__( # type: ignore def __new__( # type: ignore
cls, *, allow_blank: bool = True, strip_whitespace: bool = False, **kwargs: Any cls, **kwargs: Any
) -> BaseField: ) -> BaseField:
kwargs = { kwargs = {
**kwargs, **kwargs,
@ -254,7 +250,6 @@ class Text(ModelFieldFactory, str):
if k not in ["cls", "__class__", "kwargs"] if k not in ["cls", "__class__", "kwargs"]
}, },
} }
kwargs["allow_blank"] = kwargs.get("nullable", True)
return super().__new__(cls, **kwargs) return super().__new__(cls, **kwargs)
@classmethod @classmethod

View File

@ -8,6 +8,7 @@ from ormar.models.helpers.pydantic import (
get_potential_fields, get_potential_fields,
get_pydantic_base_orm_config, get_pydantic_base_orm_config,
get_pydantic_field, get_pydantic_field,
merge_or_generate_pydantic_config,
remove_excluded_parent_fields, remove_excluded_parent_fields,
) )
from ormar.models.helpers.relations import ( from ormar.models.helpers.relations import (
@ -33,6 +34,7 @@ __all__ = [
"get_pydantic_field", "get_pydantic_field",
"get_potential_fields", "get_potential_fields",
"get_pydantic_base_orm_config", "get_pydantic_base_orm_config",
"merge_or_generate_pydantic_config",
"check_required_meta_parameters", "check_required_meta_parameters",
"sqlalchemy_columns_from_model_fields", "sqlalchemy_columns_from_model_fields",
"populate_choices_validators", "populate_choices_validators",

View File

@ -1,3 +1,4 @@
import inspect
from typing import Dict, Optional, TYPE_CHECKING, Tuple, Type from typing import Dict, Optional, TYPE_CHECKING, Tuple, Type
import pydantic import pydantic
@ -5,6 +6,7 @@ from pydantic.fields import ModelField
from pydantic.utils import lenient_issubclass from pydantic.utils import lenient_issubclass
from ormar.fields import BaseField # noqa: I100, I202 from ormar.fields import BaseField # noqa: I100, I202
from ormar.exceptions import ModelDefinitionError
if TYPE_CHECKING: # pragma no cover if TYPE_CHECKING: # pragma no cover
from ormar import Model from ormar import Model
@ -88,6 +90,31 @@ def populate_pydantic_default_values(attrs: Dict) -> Tuple[Dict, Dict]:
return attrs, model_fields return attrs, model_fields
def merge_or_generate_pydantic_config(attrs: Dict, name: str) -> None:
"""
Checks if the user provided pydantic Config,
and if he did merges it with the default one.
Updates the attrs in place with a new config.
:rtype: None
"""
DefaultConfig = get_pydantic_base_orm_config()
if "Config" in attrs:
ProvidedConfig = attrs["Config"]
if not inspect.isclass(ProvidedConfig):
raise ModelDefinitionError(
f"Config provided for class {name} has to be a class."
)
class Config(ProvidedConfig, DefaultConfig): # type: ignore
pass
attrs["Config"] = Config
else:
attrs["Config"] = DefaultConfig
def get_pydantic_base_orm_config() -> Type[pydantic.BaseConfig]: def get_pydantic_base_orm_config() -> Type[pydantic.BaseConfig]:
""" """
Returns empty pydantic Config with orm_mode set to True. Returns empty pydantic Config with orm_mode set to True.

View File

@ -1,4 +1,3 @@
import inspect
from typing import ( from typing import (
Any, Any,
Dict, Dict,
@ -37,8 +36,8 @@ from ormar.models.helpers import (
expand_reverse_relationships, expand_reverse_relationships,
extract_annotations_and_default_vals, extract_annotations_and_default_vals,
get_potential_fields, get_potential_fields,
get_pydantic_base_orm_config,
get_pydantic_field, get_pydantic_field,
merge_or_generate_pydantic_config,
meta_field_not_set, meta_field_not_set,
populate_choices_validators, populate_choices_validators,
populate_default_options_values, populate_default_options_values,
@ -553,21 +552,7 @@ class ModelMetaclass(pydantic.main.ModelMetaclass):
:param attrs: class namespace :param attrs: class namespace
:type attrs: Dict :type attrs: Dict
""" """
DefaultConfig = get_pydantic_base_orm_config() merge_or_generate_pydantic_config(attrs=attrs, name=name)
if "Config" in attrs:
ProvidedConfig = attrs["Config"]
if not inspect.isclass(ProvidedConfig):
raise ModelDefinitionError(
f"Config provided for class {name} has to be a class."
)
class Config(ProvidedConfig, DefaultConfig): # type: ignore
pass
attrs["Config"] = Config
else:
attrs["Config"] = DefaultConfig
attrs["__name__"] = name attrs["__name__"] = name
attrs, model_fields = extract_annotations_and_default_vals(attrs) attrs, model_fields = extract_annotations_and_default_vals(attrs)
for base in reversed(bases): for base in reversed(bases):

View File

@ -0,0 +1,29 @@
import databases
import sqlalchemy
import ormar
DATABASE_URL = "sqlite:///db.sqlite"
database = databases.Database(DATABASE_URL)
metadata = sqlalchemy.MetaData()
class BaseMeta(ormar.ModelMeta):
metadata = metadata
database = database
class Author(ormar.Model):
class Meta(BaseMeta):
tablename = "authors"
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100)
contents: str = ormar.Text()
def test_schema_not_allowed():
schema = Author.schema()
for field_schema in schema.get("properties").values():
for key in field_schema.keys():
assert "_" not in key, f"Found illegal field in openapi schema: {key}"

View File

@ -595,3 +595,9 @@ async def test_get_and_first():
user = await User2.objects.first() user = await User2.objects.first()
assert user.name == "Jane" assert user.name == "Jane"
def test_constraints():
with pytest.raises(pydantic.ValidationError) as e:
Product(name="T-Shirt", rating=50, in_stock=True)
assert "ensure this value is less than or equal to 5" in str(e.value)