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
|
||||||
|
|
||||||
`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
|
||||||
@ -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
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -275,5 +275,9 @@ class ManyToManyField(ForeignKeyField, ormar.QuerySetProtocol, ormar.RelationPro
|
|||||||
"metadata": self.owner.Meta.metadata,
|
"metadata": self.owner.Meta.metadata,
|
||||||
}
|
}
|
||||||
new_meta = type("Meta", (), new_meta_namespace)
|
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)
|
self.through = cast(Type["Model"], through_model)
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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):
|
||||||
|
|||||||
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()
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user