Merge pull request #298 from collerek/bug-fixes
SmallInteger field type and ability to pass pydantic config
This commit is contained in:
@ -14,23 +14,19 @@ Each of the `Fields` has assigned both `sqlalchemy` column class and python type
|
||||
|
||||
### String
|
||||
|
||||
`String(max_length,
|
||||
allow_blank: bool = True,
|
||||
strip_whitespace: bool = False,
|
||||
`String(max_length: int,
|
||||
min_length: int = None,
|
||||
max_length: int = None,
|
||||
curtail_length: int = None,
|
||||
regex: str = None,)` has a required `max_length` parameter.
|
||||
|
||||
* Sqlalchemy column: `sqlalchemy.String`
|
||||
* Type (used for pydantic): `str`
|
||||
|
||||
!!!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(allow_blank: bool = True, strip_whitespace: bool = False)` has no required parameters.
|
||||
`Text()` has no required parameters.
|
||||
|
||||
* Sqlalchemy column: `sqlalchemy.Text`
|
||||
* Type (used for pydantic): `str`
|
||||
@ -247,5 +243,5 @@ response = client.post(
|
||||
|
||||
[relations]: ../relations/index.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
|
||||
@ -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
|
||||
|
||||
## 🐛 Fixes
|
||||
|
||||
@ -41,7 +41,6 @@ from ormar.exceptions import ( # noqa: I100
|
||||
from ormar.fields import (
|
||||
BaseField,
|
||||
BigInteger,
|
||||
SmallInteger,
|
||||
Boolean,
|
||||
DECODERS_MAP,
|
||||
Date,
|
||||
@ -57,6 +56,7 @@ from ormar.fields import (
|
||||
LargeBinary,
|
||||
ManyToMany,
|
||||
ManyToManyField,
|
||||
SmallInteger,
|
||||
String,
|
||||
Text,
|
||||
Time,
|
||||
@ -77,7 +77,7 @@ class UndefinedType: # pragma no cover
|
||||
|
||||
Undefined = UndefinedType()
|
||||
|
||||
__version__ = "0.10.15"
|
||||
__version__ = "0.10.16"
|
||||
__all__ = [
|
||||
"Integer",
|
||||
"BigInteger",
|
||||
|
||||
@ -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.model_fields import (
|
||||
BigInteger,
|
||||
SmallInteger,
|
||||
Boolean,
|
||||
Date,
|
||||
DateTime,
|
||||
@ -18,6 +17,7 @@ from ormar.fields.model_fields import (
|
||||
Integer,
|
||||
JSON,
|
||||
LargeBinary,
|
||||
SmallInteger,
|
||||
String,
|
||||
Text,
|
||||
Time,
|
||||
|
||||
@ -275,5 +275,9 @@ class ManyToManyField(ForeignKeyField, ormar.QuerySetProtocol, ormar.RelationPro
|
||||
"metadata": self.owner.Meta.metadata,
|
||||
}
|
||||
new_meta = type("Meta", (), new_meta_namespace)
|
||||
through_model = type(class_name, (ormar.Model,), {"Meta": new_meta})
|
||||
through_model = type(
|
||||
class_name,
|
||||
(ormar.Model,),
|
||||
{"Meta": new_meta, "id": ormar.Integer(name="id", primary_key=True)},
|
||||
)
|
||||
self.through = cast(Type["Model"], through_model)
|
||||
|
||||
@ -142,10 +142,7 @@ class String(ModelFieldFactory, str):
|
||||
cls,
|
||||
*,
|
||||
max_length: int,
|
||||
allow_blank: bool = True,
|
||||
strip_whitespace: bool = False,
|
||||
min_length: int = None,
|
||||
curtail_length: int = None,
|
||||
regex: str = None,
|
||||
**kwargs: Any
|
||||
) -> BaseField: # type: ignore
|
||||
@ -157,7 +154,6 @@ class String(ModelFieldFactory, str):
|
||||
if k not in ["cls", "__class__", "kwargs"]
|
||||
},
|
||||
}
|
||||
kwargs["allow_blank"] = kwargs.get("nullable", True)
|
||||
return super().__new__(cls, **kwargs)
|
||||
|
||||
@classmethod
|
||||
@ -244,7 +240,7 @@ class Text(ModelFieldFactory, str):
|
||||
_sample = "text"
|
||||
|
||||
def __new__( # type: ignore
|
||||
cls, *, allow_blank: bool = True, strip_whitespace: bool = False, **kwargs: Any
|
||||
cls, **kwargs: Any
|
||||
) -> BaseField:
|
||||
kwargs = {
|
||||
**kwargs,
|
||||
@ -254,7 +250,6 @@ class Text(ModelFieldFactory, str):
|
||||
if k not in ["cls", "__class__", "kwargs"]
|
||||
},
|
||||
}
|
||||
kwargs["allow_blank"] = kwargs.get("nullable", True)
|
||||
return super().__new__(cls, **kwargs)
|
||||
|
||||
@classmethod
|
||||
|
||||
@ -8,6 +8,7 @@ from ormar.models.helpers.pydantic import (
|
||||
get_potential_fields,
|
||||
get_pydantic_base_orm_config,
|
||||
get_pydantic_field,
|
||||
merge_or_generate_pydantic_config,
|
||||
remove_excluded_parent_fields,
|
||||
)
|
||||
from ormar.models.helpers.relations import (
|
||||
@ -33,6 +34,7 @@ __all__ = [
|
||||
"get_pydantic_field",
|
||||
"get_potential_fields",
|
||||
"get_pydantic_base_orm_config",
|
||||
"merge_or_generate_pydantic_config",
|
||||
"check_required_meta_parameters",
|
||||
"sqlalchemy_columns_from_model_fields",
|
||||
"populate_choices_validators",
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import inspect
|
||||
from typing import Dict, Optional, TYPE_CHECKING, Tuple, Type
|
||||
|
||||
import pydantic
|
||||
@ -5,6 +6,7 @@ from pydantic.fields import ModelField
|
||||
from pydantic.utils import lenient_issubclass
|
||||
|
||||
from ormar.fields import BaseField # noqa: I100, I202
|
||||
from ormar.exceptions import ModelDefinitionError
|
||||
|
||||
if TYPE_CHECKING: # pragma no cover
|
||||
from ormar import Model
|
||||
@ -88,6 +90,31 @@ def populate_pydantic_default_values(attrs: Dict) -> Tuple[Dict, Dict]:
|
||||
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]:
|
||||
"""
|
||||
Returns empty pydantic Config with orm_mode set to True.
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import inspect
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
@ -37,8 +36,8 @@ from ormar.models.helpers import (
|
||||
expand_reverse_relationships,
|
||||
extract_annotations_and_default_vals,
|
||||
get_potential_fields,
|
||||
get_pydantic_base_orm_config,
|
||||
get_pydantic_field,
|
||||
merge_or_generate_pydantic_config,
|
||||
meta_field_not_set,
|
||||
populate_choices_validators,
|
||||
populate_default_options_values,
|
||||
@ -553,21 +552,7 @@ class ModelMetaclass(pydantic.main.ModelMetaclass):
|
||||
:param attrs: class namespace
|
||||
:type attrs: Dict
|
||||
"""
|
||||
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
|
||||
|
||||
merge_or_generate_pydantic_config(attrs=attrs, name=name)
|
||||
attrs["__name__"] = name
|
||||
attrs, model_fields = extract_annotations_and_default_vals(attrs)
|
||||
for base in reversed(bases):
|
||||
|
||||
29
tests/test_fastapi/test_schema_not_allowed_params.py
Normal file
29
tests/test_fastapi/test_schema_not_allowed_params.py
Normal 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}"
|
||||
@ -595,3 +595,9 @@ async def test_get_and_first():
|
||||
|
||||
user = await User2.objects.first()
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user