add part of docstrings

This commit is contained in:
collerek
2020-12-08 16:40:15 +01:00
parent 0706306c74
commit 5c15564e0b
5 changed files with 224 additions and 0 deletions

View File

@ -1,3 +1,11 @@
"""
Module with all decorators that are exposed for users.
Currently only:
* property_field - exposing @property like function as field in Model.dict()
* predefined signals decorators (pre/post + save/update/delete)
"""
from ormar.decorators.property_field import property_field from ormar.decorators.property_field import property_field
from ormar.decorators.signals import ( from ormar.decorators.signals import (
post_delete, post_delete,

View File

@ -6,6 +6,19 @@ from ormar.exceptions import ModelDefinitionError
def property_field(func: Callable) -> Union[property, Callable]: def property_field(func: Callable) -> Union[property, Callable]:
"""
Decorator to set a property like function on Model to be exposed
as field in dict() and fastapi response.
Although you can decorate a @property field like this and this will work,
mypy validation will complain about this.
Note that "fields" exposed like this do not go through validation.
:raises: ModelDefinitionError if method has any other argument than self.
:param func: decorated function to be exposed
:type func: Callable
:return: decorated function passed in func param, with set __property_field__ = True
:rtype: Union[property, Callable]
"""
if isinstance(func, property): # pragma: no cover if isinstance(func, property): # pragma: no cover
func.fget.__property_field__ = True func.fget.__property_field__ = True
else: else:

View File

@ -7,7 +7,28 @@ if TYPE_CHECKING: # pragma: no cover
def receiver( def receiver(
signal: str, senders: Union[Type["Model"], List[Type["Model"]]] signal: str, senders: Union[Type["Model"], List[Type["Model"]]]
) -> Callable: ) -> Callable:
"""
Connect given function to all senders for given signal name.
:param signal: name of the signal to register to
:type signal: str
:param senders: one or a list of "Model" classes
that should have the signal receiver registered
:type senders: Union[Type["Model"], List[Type["Model"]]]
:return: returns the original function untouched
:rtype: Callable
"""
def _decorator(func: Callable) -> Callable: def _decorator(func: Callable) -> Callable:
"""
Internal decorator that does all the registeriing.
:param func: function to register as receiver
:type func: Callable
:return: untouched function already registered for given signal
:rtype: Callable
"""
if not isinstance(senders, list): if not isinstance(senders, list):
_senders = [senders] _senders = [senders]
else: else:
@ -21,24 +42,78 @@ def receiver(
def post_save(senders: Union[Type["Model"], List[Type["Model"]]],) -> Callable: def post_save(senders: Union[Type["Model"], List[Type["Model"]]],) -> Callable:
"""
Connect given function to all senders for post_save signal.
:param senders: one or a list of "Model" classes
that should have the signal receiver registered
:type senders: Union[Type["Model"], List[Type["Model"]]]
:return: returns the original function untouched
:rtype: Callable
"""
return receiver(signal="post_save", senders=senders) return receiver(signal="post_save", senders=senders)
def post_update(senders: Union[Type["Model"], List[Type["Model"]]],) -> Callable: def post_update(senders: Union[Type["Model"], List[Type["Model"]]],) -> Callable:
"""
Connect given function to all senders for post_update signal.
:param senders: one or a list of "Model" classes
that should have the signal receiver registered
:type senders: Union[Type["Model"], List[Type["Model"]]]
:return: returns the original function untouched
:rtype: Callable
"""
return receiver(signal="post_update", senders=senders) return receiver(signal="post_update", senders=senders)
def post_delete(senders: Union[Type["Model"], List[Type["Model"]]],) -> Callable: def post_delete(senders: Union[Type["Model"], List[Type["Model"]]],) -> Callable:
"""
Connect given function to all senders for post_delete signal.
:param senders: one or a list of "Model" classes
that should have the signal receiver registered
:type senders: Union[Type["Model"], List[Type["Model"]]]
:return: returns the original function untouched
:rtype: Callable
"""
return receiver(signal="post_delete", senders=senders) return receiver(signal="post_delete", senders=senders)
def pre_save(senders: Union[Type["Model"], List[Type["Model"]]],) -> Callable: def pre_save(senders: Union[Type["Model"], List[Type["Model"]]],) -> Callable:
"""
Connect given function to all senders for pre_save signal.
:param senders: one or a list of "Model" classes
that should have the signal receiver registered
:type senders: Union[Type["Model"], List[Type["Model"]]]
:return: returns the original function untouched
:rtype: Callable
"""
return receiver(signal="pre_save", senders=senders) return receiver(signal="pre_save", senders=senders)
def pre_update(senders: Union[Type["Model"], List[Type["Model"]]]) -> Callable: def pre_update(senders: Union[Type["Model"], List[Type["Model"]]]) -> Callable:
"""
Connect given function to all senders for pre_update signal.
:param senders: one or a list of "Model" classes
that should have the signal receiver registered
:type senders: Union[Type["Model"], List[Type["Model"]]]
:return: returns the original function untouched
:rtype: Callable
"""
return receiver(signal="pre_update", senders=senders) return receiver(signal="pre_update", senders=senders)
def pre_delete(senders: Union[Type["Model"], List[Type["Model"]]]) -> Callable: def pre_delete(senders: Union[Type["Model"], List[Type["Model"]]]) -> Callable:
"""
Connect given function to all senders for pre_delete signal.
:param senders: one or a list of "Model" classes
that should have the signal receiver registered
:type senders: Union[Type["Model"], List[Type["Model"]]]
:return: returns the original function untouched
:rtype: Callable
"""
return receiver(signal="pre_delete", senders=senders) return receiver(signal="pre_delete", senders=senders)

View File

@ -1,3 +1,9 @@
"""
Module with classes and constructors for ormar Fields.
Base Fields types (like String, Integer etc.)
as well as relation Fields (ForeignKey, ManyToMany).
Also a definition for custom CHAR based sqlalchemy UUID field
"""
from ormar.fields.base import BaseField from ormar.fields.base import BaseField
from ormar.fields.foreign_key import ForeignKey, UniqueColumns from ormar.fields.foreign_key import ForeignKey, UniqueColumns
from ormar.fields.many_to_many import ManyToMany, ManyToManyField from ormar.fields.many_to_many import ManyToMany, ManyToManyField

View File

@ -14,6 +14,16 @@ if TYPE_CHECKING: # pragma no cover
class BaseField(FieldInfo): class BaseField(FieldInfo):
"""
BaseField serves as a parent class for all basic Fields in ormar.
It keeps all common parameters available for all fields as well as
set of useful functions.
All values are kept as class variables, ormar Fields are never instantiated.
Subclasses pydantic.FieldInfo to keep the fields related
to pydantic field types like ConstrainedStr
"""
__type__ = None __type__ = None
column_type: sqlalchemy.Column column_type: sqlalchemy.Column
@ -37,14 +47,44 @@ class BaseField(FieldInfo):
@classmethod @classmethod
def is_valid_uni_relation(cls) -> bool: def is_valid_uni_relation(cls) -> bool:
"""
Checks if field is a relation definition but only for ForeignKey relation,
so excludes ManyToMany fields, as well as virtual ForeignKey
(second side of FK relation).
Is used to define if a field is a db ForeignKey column that
should be saved/populated when dealing with internal/own
Model columns only.
:return: result of the check
:rtype: bool
"""
return not issubclass(cls, ormar.fields.ManyToManyField) and not cls.virtual return not issubclass(cls, ormar.fields.ManyToManyField) and not cls.virtual
@classmethod @classmethod
def get_alias(cls) -> str: def get_alias(cls) -> str:
"""
Used to translate Model column names to database column names during db queries.
:return: returns custom database column name if defined by user,
otherwise field name in ormar/pydantic
:rtype: str
"""
return cls.alias if cls.alias else cls.name return cls.alias if cls.alias else cls.name
@classmethod @classmethod
def is_valid_field_info_field(cls, field_name: str) -> bool: def is_valid_field_info_field(cls, field_name: str) -> bool:
"""
Checks if field belongs to pydantic FieldInfo
- used during setting default pydantic values.
Excludes defaults and alias as they are populated separately
(defaults) or not at all (alias)
:param field_name: field name of BaseFIeld
:type field_name: str
:return: True if field is present on pydantic.FieldInfo
:rtype: bool
"""
return ( return (
field_name not in ["default", "default_factory", "alias"] field_name not in ["default", "default_factory", "alias"]
and not field_name.startswith("__") and not field_name.startswith("__")
@ -53,6 +93,17 @@ class BaseField(FieldInfo):
@classmethod @classmethod
def convert_to_pydantic_field_info(cls, allow_null: bool = False) -> FieldInfo: def convert_to_pydantic_field_info(cls, allow_null: bool = False) -> FieldInfo:
"""
Converts a BaseField into pydantic.FieldInfo
that is later easily processed by pydantic.
Used in an ormar Model Metaclass.
:param allow_null: flag if the default value can be None
or if it should be populated by pydantic Undefined
:type allow_null: bool
:return: actual instance of pydantic.FieldInfo with all needed fields populated
:rtype: pydantic.FieldInfo
"""
base = cls.default_value() base = cls.default_value()
if base is None: if base is None:
base = ( base = (
@ -67,6 +118,23 @@ class BaseField(FieldInfo):
@classmethod @classmethod
def default_value(cls, use_server: bool = False) -> Optional[FieldInfo]: def default_value(cls, use_server: bool = False) -> Optional[FieldInfo]:
"""
Returns a FieldInfo instance with populated default
(static) or default_factory (function).
If the field is a autoincrement primary key the default is None.
Otherwise field have to has either default, or default_factory populated.
If all default conditions fail None is returned.
Used in converting to pydantic FieldInfo.
:param use_server: flag marking if server_default should be
treated as default value, default False
:type use_server: bool
:return: returns a call to pydantic.Field
which is returning a FieldInfo instance
:rtype: Optional[pydantic.FieldInfo]
"""
if cls.is_auto_primary_key(): if cls.is_auto_primary_key():
return Field(default=None) return Field(default=None)
if cls.has_default(use_server=use_server): if cls.has_default(use_server=use_server):
@ -78,6 +146,17 @@ class BaseField(FieldInfo):
@classmethod @classmethod
def get_default(cls, use_server: bool = False) -> Any: # noqa CCR001 def get_default(cls, use_server: bool = False) -> Any: # noqa CCR001
"""
Return default value for a field.
If the field is Callable the function is called and actual result is returned.
Used to populate default_values for pydantic Model in ormar Model Metaclass.
:param use_server: flag marking if server_default should be
treated as default value, default False
:type use_server: bool
:return: default value for the field if set, otherwise implicit None
:rtype: Any
"""
if cls.has_default(): if cls.has_default():
default = ( default = (
cls.default cls.default
@ -90,18 +169,45 @@ class BaseField(FieldInfo):
@classmethod @classmethod
def has_default(cls, use_server: bool = True) -> bool: def has_default(cls, use_server: bool = True) -> bool:
"""
Checks if the field has default value set.
:param use_server: flag marking if server_default should be
treated as default value, default False
:type use_server: bool
:return: result of the check if default value is set
:rtype: bool
"""
return cls.default is not None or ( return cls.default is not None or (
cls.server_default is not None and use_server cls.server_default is not None and use_server
) )
@classmethod @classmethod
def is_auto_primary_key(cls) -> bool: def is_auto_primary_key(cls) -> bool:
"""
Checks if field is first a primary key and if it,
it's than check if it's set to autoincrement.
Autoincrement primary_key is nullable/optional.
:return: result of the check for primary key and autoincrement
:rtype: bool
"""
if cls.primary_key: if cls.primary_key:
return cls.autoincrement return cls.autoincrement
return False return False
@classmethod @classmethod
def get_column(cls, name: str) -> sqlalchemy.Column: def get_column(cls, name: str) -> sqlalchemy.Column:
"""
Returns definition of sqlalchemy.Column used in creation of sqlalchemy.Table.
Populates name, column type constraints, as well as a number of parameters like
primary_key, index, unique, nullable, default and server_default.
:param name: name of the db column - used if alias is not set
:type name: str
:return: actual definition of the database column as sqlalchemy requires.
:rtype: sqlalchemy.Column
"""
return sqlalchemy.Column( return sqlalchemy.Column(
cls.alias or name, cls.alias or name,
cls.column_type, cls.column_type,
@ -118,4 +224,20 @@ class BaseField(FieldInfo):
def expand_relationship( def expand_relationship(
cls, value: Any, child: Union["Model", "NewBaseModel"], to_register: bool = True cls, value: Any, child: Union["Model", "NewBaseModel"], to_register: bool = True
) -> Any: ) -> Any:
"""
Function overwritten for relations, in basic field the value is returned as is.
For relations the child model is first constructed (if needed),
registered in relation and returned.
For relation fields the value can be a pk value (Any type of field),
dict (from Model) or actual instance/list of a "Model".
:param value: a Model field value, returned untouched for non relation fields.
:type value: Any
:param child: a child Model to register
:type child: Union["Model", "NewBaseModel"]
:param to_register: flag if the relation should be set in RelationshipManager
:type to_register: bool
:return: returns untouched value for normal fields, expands only for relations
:rtype: Any
"""
return value return value