From 5c15564e0b60d5962d44749aa4610f42f06c27bc Mon Sep 17 00:00:00 2001 From: collerek Date: Tue, 8 Dec 2020 16:40:15 +0100 Subject: [PATCH] add part of docstrings --- ormar/decorators/__init__.py | 8 ++ ormar/decorators/property_field.py | 13 +++ ormar/decorators/signals.py | 75 ++++++++++++++++++ ormar/fields/__init__.py | 6 ++ ormar/fields/base.py | 122 +++++++++++++++++++++++++++++ 5 files changed, 224 insertions(+) diff --git a/ormar/decorators/__init__.py b/ormar/decorators/__init__.py index 395e3e2..2b5e668 100644 --- a/ormar/decorators/__init__.py +++ b/ormar/decorators/__init__.py @@ -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.signals import ( post_delete, diff --git a/ormar/decorators/property_field.py b/ormar/decorators/property_field.py index a377202..732cff7 100644 --- a/ormar/decorators/property_field.py +++ b/ormar/decorators/property_field.py @@ -6,6 +6,19 @@ from ormar.exceptions import ModelDefinitionError 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 func.fget.__property_field__ = True else: diff --git a/ormar/decorators/signals.py b/ormar/decorators/signals.py index 0505e1a..24f5ce4 100644 --- a/ormar/decorators/signals.py +++ b/ormar/decorators/signals.py @@ -7,7 +7,28 @@ if TYPE_CHECKING: # pragma: no cover def receiver( signal: str, senders: Union[Type["Model"], List[Type["Model"]]] ) -> 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: + """ + + 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): _senders = [senders] else: @@ -21,24 +42,78 @@ def receiver( 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) 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) 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) 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) 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) 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) diff --git a/ormar/fields/__init__.py b/ormar/fields/__init__.py index 325fcf6..681cb40 100644 --- a/ormar/fields/__init__.py +++ b/ormar/fields/__init__.py @@ -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.foreign_key import ForeignKey, UniqueColumns from ormar.fields.many_to_many import ManyToMany, ManyToManyField diff --git a/ormar/fields/base.py b/ormar/fields/base.py index e902aa0..343e7f9 100644 --- a/ormar/fields/base.py +++ b/ormar/fields/base.py @@ -14,6 +14,16 @@ if TYPE_CHECKING: # pragma no cover 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 column_type: sqlalchemy.Column @@ -37,14 +47,44 @@ class BaseField(FieldInfo): @classmethod 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 @classmethod 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 @classmethod 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 ( field_name not in ["default", "default_factory", "alias"] and not field_name.startswith("__") @@ -53,6 +93,17 @@ class BaseField(FieldInfo): @classmethod 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() if base is None: base = ( @@ -67,6 +118,23 @@ class BaseField(FieldInfo): @classmethod 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(): return Field(default=None) if cls.has_default(use_server=use_server): @@ -78,6 +146,17 @@ class BaseField(FieldInfo): @classmethod 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(): default = ( cls.default @@ -90,18 +169,45 @@ class BaseField(FieldInfo): @classmethod 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 ( cls.server_default is not None and use_server ) @classmethod 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: return cls.autoincrement return False @classmethod 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( cls.alias or name, cls.column_type, @@ -118,4 +224,20 @@ class BaseField(FieldInfo): def expand_relationship( cls, value: Any, child: Union["Model", "NewBaseModel"], to_register: bool = True ) -> 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