示例#1
0
def test_sa_model_info_extraction__STI_Employee():
    """ Test sa_model_info(STI_Employee) """
    assert set(sa_model_info(STI_Employee, types=AttributeType.ALL,
                             exclude=())) == {
                                 'id',
                                 'name',
                                 'type',
                             }

    assert set(sa_model_info(STI_Manager, types=AttributeType.ALL,
                             exclude=())) == {
                                 'id',
                                 'name',
                                 'type',  # employee fields
                                 # additional fields
                                 'manager_data',
                                 'company_id',
                                 'company',
                             }

    assert set(sa_model_info(STI_Engineer, types=AttributeType.ALL,
                             exclude=())) == {
                                 'id',
                                 'name',
                                 'type',  # employee fields
                                 # additional fields
                                 'engineer_info',
                             }
示例#2
0
def sa_model_fields(
    Model: type,
    *,
    types: AttributeType = AttributeType.COLUMN,
    make_optional: FilterFunctionT,
    only_readable: bool = False,
    only_writable: bool = False,
    exclude: FilterT = (),
    can_omit_nullable: bool = True,
    naming: ModelNameMakerFunction,
) -> Dict[str, Tuple[type, Field]]:
    """ Take an SqlAlchemy model and generate pydantic Field()s from it

    It will use sa_model_info() to extract attribute information from the SqlAlchemy model.
    Only fields selected by `types` & `exclude` will be considered.
    If SqlAlchemy model contains type annotations, they will override column types.

    Args:
        Model: the model to generate fields from
        types: attribute types to include. See AttributeType
        make_optional: a function(name)->bool that selects fields to make Optional[]
        only_readable: only include fields that are readable
        only_writable: only include fields that are writable
        exclude: the list of fields to ignore, or a filter(name) to exclude fields dynamically.
            See also: sa2schema.filters for useful presets
        can_omit_nullable: `False` to make nullable fields and fields with defaults required.
        naming: optionally, a callable(Model) naming pattern generator. This is required for resolving relationship targets.
            If relationships aren't used, provide some exception thrower.
    Returns:
        a dict: attribute names => (type, Field)
    """
    # Model annotations will override any Column types
    model_annotations = getattr(Model, '__annotations__', {})
    model_annotations = resolve_annotations(model_annotations,
                                            Model.__module__)

    # Walk attributes
    attributes = [
        (name, info, make_optional(name)) for name, info in sa_model_info(
            Model, types=types, exclude=exclude).items()
        if (not only_readable or info.readable) and (
            not only_writable or info.writable) and
        # Hardcoded for now.
        (not name.startswith('_')
         )  # exclude private properties. Consistent with Pydantic behavior.
    ]

    # Generate Field()s
    return {
        name: (
            # Field type
            pydantic_field_type(name, info, model_annotations, made_optional,
                                naming),
            # Field() object
            make_field(info,
                       made_optional,
                       can_omit_nullable=can_omit_nullable),
        )
        for name, info, made_optional in attributes
    }
示例#3
0
    def __init__(self, instance: object, *, copy: bool = False):
        """ Make a lightweight snapshot of an instance.

        Be sure to do it before flush(), because flush() will erase all in-memory changes.

        Args:
            instance: The instance to get the historical values for.
            copy: Copy every mutable value.
                Useful for embedded dictionaries, but it a bit more expensive, so disabled by default.
        """
        # Model info
        self.__model_info = sa_model_info(type(instance),
                                          types=AttributeType.ALL)

        # Remember the historical values
        self.__state: InstanceState = instance_state(instance)
        self.__historical = {
            # Merging dictionaries is very cheap
            **self.__state.dict,  # current values
            **self.__state.committed_state,  # overwritten with DB values
        }

        # Make a deep copy to preserve embedded dictionaries
        if copy:
            self.__historical = deepcopy(self.__historical)
示例#4
0
def test_sa_model_info_extraction__JTI_Company():
    """ Test sa_model_info(JTI_Company) """
    generated_fields = sa_model_info(JTI_Company,
                                     types=AttributeType.ALL,
                                     exclude=())
    expected_fields = {
        'id':
        ColumnInfo(
            attribute_type=AttributeType.COLUMN,
            attribute=JTI_Company.id,
            primary_key=True,
            foreign_key=False,
            nullable=False,  # primary key
            readable=True,
            writable=True,
            value_type=int,
            default=NOT_PROVIDED,  # because not nullable
            default_factory=None,
            doc=None),
        'name':
        ColumnInfo(attribute_type=AttributeType.COLUMN,
                   attribute=JTI_Company.name,
                   primary_key=False,
                   foreign_key=False,
                   nullable=True,
                   readable=True,
                   writable=True,
                   value_type=str,
                   default=None,
                   default_factory=None,
                   doc=None),
        'employees':
        RelationshipInfo(attribute_type=AttributeType.RELATIONSHIP,
                         attribute=JTI_Company.employees,
                         nullable=False,
                         readable=True,
                         writable=True,
                         value_type=List[JTI_Employee],
                         target_model=JTI_Employee,
                         uselist=True,
                         collection_class=list,
                         default=NOT_PROVIDED,
                         default_factory=list,
                         doc=None),
    }

    assert generated_fields == expected_fields

    # Compare final values
    assert {
        name: attr.final_value_type
        for name, attr in generated_fields.items()
    } == {
        'id': int,
        'name': Optional[str],
        'employees': List[JTI_Employee],
    }
示例#5
0
def primary_and_foreign_keys_from(model: type) -> Iterable[sa.orm.attributes.InstrumentedAttribute]:
    """ Get model attributes: primary and foreign keys """
    columns_info = sa2schema.sa_model_info(model, types=sa2schema.AttributeType.COLUMN)
    attr_info: sa2schema.info.ColumnInfo
    return [
        attr_info.attribute
        for attr_name, attr_info in columns_info.items()
        if attr_info.primary_key or attr_info.foreign_key
    ]
示例#6
0
def test_sa_model_info_extraction__JTI_Employee():
    """ Test sa_model_info(JTI_Company) """
    generated_fields = sa_model_info(JTI_Employee,
                                     types=AttributeType.ALL,
                                     exclude=())
    assert set(generated_fields) == {
        'id',
        'name',
        'type',
        'company_id',
        'company',
    }
    assert generated_fields['company'] == RelationshipInfo(
        attribute_type=AttributeType.RELATIONSHIP,
        attribute=JTI_Employee.company,
        nullable=True,
        readable=True,
        writable=True,
        value_type=JTI_Company,
        target_model=JTI_Company,
        uselist=False,
        collection_class=None,
        default=None,
        default_factory=None,
        doc=None)

    # Engineer is the same: inherits fields
    generated_fields = sa_model_info(JTI_Engineer,
                                     types=AttributeType.ALL,
                                     exclude=())
    assert set(generated_fields) == {
        'id',
        'name',
        'type',
        'company_id',
        'company',  # inherited
        # plus one more field
        'engineer_name',
    }
示例#7
0
def get_all_safely_loadable_properties(Model: type):
    """ Get all properties with @loads_attributes

    Returns:
        { property-name => set(attribute-names) }
    """
    all_properties = sa2.sa_model_info(Model,
                                       types=AttributeType.PROPERTY_R
                                       | AttributeType.HYBRID_PROPERTY_R)
    return {
        property_name: property_info.loads_attributes
        for property_name, property_info in all_properties.items()
        if property_info.loads_attributes
    }
示例#8
0
 def from_sa_model(cls, model: type):
     """ Extract structural information from a pydantic model """
     info = sa_model_info(model, types=AttributeType.ALL)
     return cls(
         name=model.__name__,
         docstring=model.__doc__ or '',
         fields=[
             ModelFieldInfo(
                 name=name,
                 type=attr_info.final_value_type,
                 comment=attr_info.doc,
             )
             for name, attr_info in info.items()
         ]
     )
示例#9
0
def validate_saves_custom_fields_handles_every_relationship(settings: CrudSettings, CrudHandler: type):
    """ Validate: every writable relationship is covered by @saves_custom_fields()  """
    # Only validate if going to do saving
    if any([settings.CreateInputSchema, settings.UpdateInputSchema, settings.CreateOrUpdateInputSchema]):
        # Get all relationships
        relationship_names = set(sa2schema.sa_model_info(settings.Model, types=sa2schema.AttributeType.RELATIONSHIP))

        # Get relationship names covered by somethind
        handled_relationships = set()
        # @saves_custom_fields
        handled_relationships |= crudbase.saves_custom_fields.all_field_names_from(CrudHandler)
        # exclude
        handled_relationships |= settings._exclude_on_create | settings._exclude_on_update

        # Compare
        unhandled_relationships = relationship_names - handled_relationships
        assert not unhandled_relationships, (
            f'The following relationships have not been handled by @saves_custom_fields(): '
            f'{unhandled_relationships!r}. '
            f'Either implement save handlers with @saves_custom_fields(), '
            f'or exclude them explicitly (`ro_relations`).'
        )
示例#10
0
 def for_model(self, Model: SAModelT):
     super().for_model(Model)
     self.model_info = sa2schema.sa_model_info(Model,
                                               types=AttributeType.ALL)
示例#11
0
 def for_model(self, Model: SAModelT):
     super().for_model(Model)
     self.model_info = sa2schema.sa_model_info(Model,
                                               types=self.types,
                                               exclude=self.exclude)
示例#12
0
def test_field_filter(exclude: FilterT, expected_fields: Set[str]):
    model_info = sa2.sa_model_info(models.User,
                                   types=AttributeType.ALL,
                                   exclude=exclude)
    assert set(model_info) == set(expected_fields)
示例#13
0
def test_sa_model_info_extraction__User():
    """ Test sa_model_info(User) """
    generated_fields = sa_model_info(User, types=AttributeType.ALL, exclude=())
    expected_fields = {
        '_ignored':
        ColumnInfo(  # not ignored in sa_model_info() ; ignored in sa_model()
            attribute_type=AttributeType.COLUMN,
            attribute=User._ignored,
            primary_key=False,
            foreign_key=False,
            nullable=True,
            readable=True,
            writable=True,
            value_type=str,
            default=None,
            default_factory=None,
            doc=None),
        'annotated_int':
        ColumnInfo(
            attribute_type=AttributeType.COLUMN,
            attribute=User.annotated_int,
            primary_key=True,
            foreign_key=False,
            nullable=False,
            readable=True,
            writable=True,
            value_type=str,  # annotations are ignored here
            default=NOT_PROVIDED,
            default_factory=None,
            doc=None),
        'int':
        ColumnInfo(
            attribute_type=AttributeType.COLUMN,
            attribute=User.int,
            primary_key=False,
            foreign_key=False,
            nullable=True,
            readable=True,
            writable=True,
            value_type=int,  # type is here
            default=None,  # nullable columns always have this default
            default_factory=None,
            doc=None),
        'enum':
        ColumnInfo(
            attribute_type=AttributeType.COLUMN,
            attribute=User.enum,
            primary_key=False,
            foreign_key=False,
            nullable=True,
            readable=True,
            writable=True,
            value_type=EnumType,  # type is here
            default=None,  # nullable column
            default_factory=None,
            doc=None),
        'optional':
        ColumnInfo(
            attribute_type=AttributeType.COLUMN,
            attribute=User.optional,
            primary_key=False,
            foreign_key=False,
            nullable=True,  # optional
            readable=True,
            writable=True,
            value_type=str,  # type is not wrapped in Optional[]
            default=None,  # nullable column
            default_factory=None,
            doc=None),
        'required':
        ColumnInfo(
            attribute_type=AttributeType.COLUMN,
            attribute=User.required,
            primary_key=False,
            foreign_key=False,
            nullable=False,  # required
            readable=True,
            writable=True,
            value_type=str,
            default=NOT_PROVIDED,  # non-nullable columns get this
            default_factory=None,
            doc=None),
        'default':
        ColumnInfo(
            attribute_type=AttributeType.COLUMN,
            attribute=User.default,
            primary_key=False,
            foreign_key=False,
            nullable=False,  # not nullable
            readable=True,
            writable=True,
            value_type=str,
            default='value',  # default value
            default_factory=None,
            doc=None),
        'documented':
        ColumnInfo(
            attribute_type=AttributeType.COLUMN,
            attribute=User.documented,
            primary_key=False,
            foreign_key=False,
            nullable=True,
            readable=True,
            writable=True,
            value_type=str,
            default=None,  # nullable column
            default_factory=None,
            doc='Some descriptive text'  # doc=text
        ),
        'json_attr':
        ColumnInfo(
            attribute_type=AttributeType.COLUMN,
            attribute=User.json_attr,
            primary_key=False,
            foreign_key=False,
            nullable=True,
            readable=True,
            writable=True,
            # JSON defaults to dict in SqlAlchemy
            value_type=dict,
            default=None,  # nullable column
            default_factory=None,
            doc=None),
        'property_without_type':
        PropertyInfo(
            attribute_type=AttributeType.PROPERTY_R,  # no setter
            attribute=User.property_without_type,
            nullable=True,  # no type. No idea. May be null as well.
            readable=True,
            writable=False,  # no setter
            loads_attributes=None,
            value_type=Any,  # no idea
            default=NOT_PROVIDED,
            default_factory=None,
            doc=None),
        'property_typed':
        PropertyInfo(
            attribute_type=AttributeType.PROPERTY_R,  # no setter
            attribute=User.property_typed,
            nullable=False,  # return value is not Optional[]
            readable=True,
            writable=False,  # no setter
            loads_attributes=None,
            value_type=str,
            default=NOT_PROVIDED,
            default_factory=None,
            doc=None),
        'property_documented':
        PropertyInfo(
            attribute_type=AttributeType.PROPERTY_R,  # no setter
            attribute=User.property_documented,
            nullable=True,  # no return value. May be null.
            readable=True,
            writable=False,  # no setter
            loads_attributes={'documented'},  # read from @loads_attributes()
            value_type=Any,  # no return value
            default=NOT_PROVIDED,
            default_factory=None,
            doc=' Documented property '),
        'property_nullable':
        PropertyInfo(
            attribute_type=AttributeType.PROPERTY_R,  # no setter
            attribute=User.property_nullable,
            nullable=True,  # explicitly Optional[]
            readable=True,
            writable=False,  # no setter
            loads_attributes=None,
            value_type=str,  # unwrapped
            default=NOT_PROVIDED,
            default_factory=None,
            doc=None,
        ),
        'property_writable':
        PropertyInfo(
            attribute_type=AttributeType.PROPERTY_RW,  # setter provided
            attribute=User.property_writable,
            nullable=False,  # no Optional[]
            readable=True,
            writable=True,  # with setter
            loads_attributes=None,
            value_type=str,  # type
            default='default',  # from setter's argument
            default_factory=None,
            doc=None,
        ),
        'hybrid_property_typed':
        HybridPropertyInfo(
            attribute_type=AttributeType.HYBRID_PROPERTY_R,  # no setter
            attribute=User.hybrid_property_typed.descriptor,
            nullable=False,  # no Optional[]
            readable=True,
            writable=False,  # no setter
            loads_attributes=None,
            value_type=str,  # type
            default=NOT_PROVIDED,
            default_factory=None,
            doc=None,
        ),
        'hybrid_property_writable':
        HybridPropertyInfo(
            attribute_type=AttributeType.HYBRID_PROPERTY_RW,  # setter
            attribute=User.hybrid_property_writable.descriptor,
            nullable=False,  # no Optional[]
            readable=True,
            writable=True,  # setter
            loads_attributes=None,
            value_type=str,  # type
            default='default',  # from setter's argument
            default_factory=None,
            doc=None,
        ),
        'hybrid_method_attr':
        HybridMethodInfo(
            attribute_type=AttributeType.HYBRID_METHOD,
            attribute=User.__mapper__.all_orm_descriptors.hybrid_method_attr,
            nullable=True,
            readable=True,
            writable=False,
            value_type=Any,  # no return annotation
            default=NOT_PROVIDED,
            default_factory=None,
            doc=None,
        ),
        'expression':
        ColumnExpressionInfo(
            attribute_type=AttributeType.EXPRESSION,
            attribute=User.expression,
            nullable=True,
            readable=True,
            writable=False,
            value_type=int,  # sqlalchemy knows!
            default=NOT_PROVIDED,
            default_factory=None,
            doc=None,
        ),
        'point':
        CompositeInfo(
            attribute_type=AttributeType.COMPOSITE,
            attribute=User.point,
            nullable=False,  # composites aren't nullable
            readable=True,
            writable=True,  # it's wriable
            value_type=Point,  # composite type
            default=NOT_PROVIDED,
            default_factory=None,
            doc=None,
        ),
        'synonym':
        CompositeInfo(
            # COMPLETELY copies the 'point' it points to!
            attribute_type=AttributeType.COMPOSITE,
            attribute=User.synonym,
            nullable=False,
            readable=True,
            writable=True,
            value_type=Point,
            default=NOT_PROVIDED,
            default_factory=None,
            doc=None,
        ),
        'articles_list':
        RelationshipInfo(
            attribute_type=AttributeType.RELATIONSHIP,
            attribute=User.articles_list,
            nullable=False,  # "no" when `uselist`
            readable=True,
            writable=True,
            value_type=List[Article],  # target model, collection
            target_model=Article,
            uselist=True,
            collection_class=list,
            default=NOT_PROVIDED,
            default_factory=list,
            doc=None,
        ),
        'articles_set':
        RelationshipInfo(
            attribute_type=AttributeType.RELATIONSHIP,
            attribute=User.articles_set,
            nullable=False,  # "no" when `uselist`
            readable=True,
            writable=False,  # `viewonly` is set
            value_type=Set[Article],  # target model, collection
            target_model=Article,
            uselist=True,
            collection_class=set,
            default=NOT_PROVIDED,
            default_factory=set,
            doc=None,
        ),
        'articles_dict_attr':
        RelationshipInfo(
            attribute_type=AttributeType.RELATIONSHIP,
            attribute=User.articles_dict_attr,
            nullable=False,  # "no" when `uselist`
            readable=True,
            writable=True,
            value_type=Dict[Any, Article],  # guessed the type!
            target_model=Article,
            uselist=True,
            collection_class=User.articles_dict_attr.property.
            collection_class,  # some weird class
            default=NOT_PROVIDED,
            default_factory=dict,
            doc=None,
        ),
        'articles_dict_keyfun':
        RelationshipInfo(
            attribute_type=AttributeType.RELATIONSHIP,
            attribute=User.articles_dict_keyfun,
            nullable=False,  # "no" when `uselist`
            readable=True,
            writable=True,
            value_type=Dict[Any, Article],  # guessed the type!
            target_model=Article,
            uselist=True,
            collection_class=User.articles_dict_keyfun.property.
            collection_class,  # some weird callable
            default=NOT_PROVIDED,
            default_factory=dict,
            doc=None,
        ),
        'article_titles':
        AssociationProxyInfo(
            attribute_type=AttributeType.ASSOCIATION_PROXY,
            attribute=User.article_titles,
            nullable=False,  # "yes" when `scalar`
            readable=True,
            writable=False,  # always false
            value_type=List[str],  # dict: target column's type, target model
            target_model=Article,
            collection_class=list,
            default=NOT_PROVIDED,
            default_factory=list,
            doc=None,
            target_attr_info=ColumnInfo(attribute_type=AttributeType.COLUMN,
                                        attribute=Article.title,
                                        primary_key=False,
                                        foreign_key=False,
                                        nullable=True,
                                        readable=True,
                                        writable=True,
                                        value_type=str,
                                        default=None,
                                        default_factory=None,
                                        doc=None),
        ),
        'article_authors':
        AssociationProxyInfo(
            attribute_type=AttributeType.ASSOCIATION_PROXY,
            attribute=User.article_authors,
            nullable=False,
            readable=True,
            writable=False,  # always false
            value_type=List[User],  # dict: target column's type, target model
            target_model=Article,
            collection_class=list,
            default=NOT_PROVIDED,
            default_factory=list,
            doc=None,
            target_attr_info=RelationshipInfo(
                attribute_type=AttributeType.RELATIONSHIP,
                attribute=Article.user,
                nullable=True,
                readable=True,
                writable=True,
                value_type=User,
                target_model=User,
                uselist=False,
                collection_class=None,
                default=None,
                default_factory=None,
                doc=None,
            ),
        ),
        'articles_q':
        DynamicLoaderInfo(
            attribute_type=AttributeType.DYNAMIC_LOADER,
            attribute=User.articles_q,
            nullable=False,  # "no" when `uselist`
            readable=True,
            writable=True,  # yes it is!
            value_type=List[Article],  # target model, collection
            target_model=Article,
            uselist=True,
            collection_class=list,
            default=NOT_PROVIDED,
            default_factory=list,
            doc=None,
        ),
    }

    # Compare keys first
    assert set(generated_fields) == set(expected_fields)

    # Compare values
    if False:
        assert generated_fields == expected_fields
    # Compare values one by one
    # May be easier to debug when the difference is too large
    else:
        for k in generated_fields:
            assert (k, generated_fields[k]) == (k, expected_fields[k])

    # Compare final values
    assert {
        name: attr.final_value_type
        for name, attr in generated_fields.items()
    } == {
        '_ignored': Optional[str],
        'annotated_int': str,
        'int': Optional[int],
        'enum': Optional[EnumType],
        'optional': Optional[str],
        'required': str,
        'default': str,
        'documented': Optional[str],
        'json_attr': Optional[dict],
        'property_without_type':
        Any,  # note: `Any` is not wrapped into Optional[]
        'property_typed': str,
        'property_documented':
        Any,  # note: `Any` is not wrapped into Optional[]
        'property_nullable': Optional[str],
        'property_writable': str,
        'hybrid_property_typed': str,
        'hybrid_property_writable': str,
        'hybrid_method_attr': Any,
        'expression': Optional[int],
        'point': Point,
        'synonym': Point,
        'articles_list': List[Article],
        'articles_set': Set[Article],
        'articles_dict_attr': Dict[Any, Article],
        'articles_dict_keyfun': Dict[Any, Article],
        'article_titles': List[str],
        'article_authors': List[User],
        'articles_q': List[Article],
    }

    # Test sa_attribute_info()
    for attribute_name, expected_attribute_info in expected_fields.items():
        assert sa_attribute_info(User,
                                 attribute_name) == expected_attribute_info

    # Test primary key
    assert sa_model_primary_key_info(User) == {
        'annotated_int': expected_fields['annotated_int']
    }

    # Test sa_model_attributes_by_type()
    attrs_by_type = sa_model_attributes_by_type(User)

    assert set(attrs_by_type) == {
        type(attr_info)
        for attr_info in expected_fields.values()
    }

    assert attrs_by_type == {
        AttributeInfoType: {
            attr_name: attr_info
            for attr_name, attr_info in expected_fields.items()
            if type(attr_info) ==
            AttributeInfoType  # don't use isinstance() because DynamicLoader will be part of relationship then
        }
        for AttributeInfoType in set(attrs_by_type)
    }
示例#14
0
def test_sa_model_info_arguments():
    """ Test sa_model_info() targeting arguments """

    assert set(sa_model_info(User, types=AttributeType.COLUMN)) == {
        '_ignored',
        'annotated_int',
        'int',
        'enum',
        'optional',
        'required',
        'default',
        'documented',
        'json_attr',
    }

    assert set(
        sa_model_info(User,
                      types=AttributeType.COLUMN,
                      exclude=('int', 'enum', 'json_attr'))) == {
                          '_ignored',
                          'annotated_int',
                          'optional',
                          'required',
                          'default',
                          'documented',
                      }

    assert set(sa_model_info(User, types=AttributeType.PROPERTY_R)) == {
        # only readable
        'property_without_type',
        'property_typed',
        'property_documented',
        'property_nullable',
        'property_writable',  # both readable and writable
    }

    assert set(sa_model_info(User, types=AttributeType.PROPERTY_W)) == {
        # only writable
        'property_writable',  # fine selection
    }

    assert set(sa_model_info(User, types=AttributeType.PROPERTY_RW)) == {
        # both readable and writable
        'property_without_type',
        'property_typed',
        'property_documented',
        'property_nullable',
        'property_writable',
        'property_writable',
    }

    assert set(sa_model_info(User, types=AttributeType.HYBRID_PROPERTY_R)) == {
        'hybrid_property_typed',
        'hybrid_property_writable',
    }

    assert set(sa_model_info(User, types=AttributeType.HYBRID_PROPERTY_W)) == {
        'hybrid_property_writable',
    }

    assert set(sa_model_info(User,
                             types=AttributeType.HYBRID_PROPERTY_RW)) == {
                                 'hybrid_property_typed',
                                 'hybrid_property_writable',
                             }

    assert set(sa_model_info(User, types=AttributeType.RELATIONSHIP)) == {
        'articles_list',
        'articles_set',
        'articles_dict_attr',
        'articles_dict_keyfun',
    }

    assert set(sa_model_info(User, types=AttributeType.DYNAMIC_LOADER)) == {
        'articles_q',
    }

    assert set(sa_model_info(User, types=AttributeType.ASSOCIATION_PROXY)) == {
        'article_titles',
        'article_authors',
    }

    assert set(sa_model_info(User, types=AttributeType.COMPOSITE)) == {
        'point',
        'synonym',
    }

    assert set(sa_model_info(User, types=AttributeType.EXPRESSION)) == {
        'expression',
    }

    assert set(sa_model_info(User, types=AttributeType.HYBRID_METHOD)) == {
        'hybrid_method_attr',
    }
示例#15
0
def test_sa_model_info_extractin__Number():
    """ Test sa_model_info(Number): test for defaults """
    common_field_info = dict(attribute_type=AttributeType.COLUMN,
                             primary_key=False,
                             foreign_key=False,
                             readable=True,
                             writable=True,
                             value_type=int,
                             doc=None)

    generated_fields = sa_model_info(Number,
                                     types=AttributeType.ALL,
                                     exclude=())
    expected_fields = {
        'id':
        ColumnInfo(
            **{
                **common_field_info,
                **dict(
                    attribute=Number.id,
                    primary_key=True,
                    nullable=False,  # primary key
                    default=NOT_PROVIDED,  # because not nullable
                    default_factory=None,
                ),
            }),
        'n':
        ColumnInfo(
            attribute=Number.n,
            nullable=True,  # nullable
            default=None,  # because nullable
            default_factory=None,
            **common_field_info),
        'nd1':
        ColumnInfo(attribute=Number.nd1,
                   nullable=True,
                   default=100,
                   default_factory=None,
                   **common_field_info),
        'nd2':
        ColumnInfo(
            attribute=Number.nd2,
            nullable=True,
            default=NOT_PROVIDED,  # we don't work with callables
            default_factory=None,
            **common_field_info),
        'nd3':
        ColumnInfo(
            attribute=Number.nd3,
            nullable=True,
            default=NOT_PROVIDED,  # we don't work with expressions
            default_factory=None,
            **common_field_info),
        'd1':
        ColumnInfo(attribute=Number.d1,
                   nullable=False,
                   default=100,
                   default_factory=None,
                   **common_field_info),
        'd2':
        ColumnInfo(
            attribute=Number.d2,
            nullable=False,
            default=NOT_PROVIDED,  # we don't work with callables
            default_factory=None,
            **common_field_info),
        'd3':
        ColumnInfo(
            attribute=Number.d3,
            nullable=False,
            default=NOT_PROVIDED,  # we don't work with expressions
            default_factory=None,
            **common_field_info),
    }

    assert generated_fields == expected_fields

    # Test primary key
    assert sa_model_primary_key_info(Number) == {'id': expected_fields['id']}
示例#16
0
def test_sa_model_info_extractin__Article():
    """ Test sa_model_info(Article) """
    generated_fields = sa_model_info(Article,
                                     types=AttributeType.ALL,
                                     exclude=())
    expected_fields = {
        'id':
        ColumnInfo(
            attribute_type=AttributeType.COLUMN,
            attribute=Article.id,
            primary_key=True,
            foreign_key=False,
            nullable=False,  # primary key
            readable=True,
            writable=True,
            value_type=int,
            default=NOT_PROVIDED,  # because not nullable
            default_factory=None,
            doc=None),
        'user_id':
        ColumnInfo(
            attribute_type=AttributeType.COLUMN,
            attribute=Article.user_id,
            primary_key=False,
            foreign_key=True,
            nullable=True,
            readable=True,
            writable=True,
            value_type=str,  # Note: gotten through a ForeinKey()
            default=None,  # because nullable
            default_factory=None,
            doc=None),
        'title':
        ColumnInfo(
            attribute_type=AttributeType.COLUMN,
            attribute=Article.title,
            primary_key=False,
            foreign_key=False,
            nullable=True,
            readable=True,
            writable=True,
            value_type=str,
            default=None,  # because nullable
            default_factory=None,
            doc=None),
        'user':
        RelationshipInfo(
            attribute_type=AttributeType.RELATIONSHIP,
            attribute=Article.user,
            nullable=True,  # singular
            readable=True,
            writable=True,
            value_type=User,  # not wrapped in any collections
            target_model=User,
            uselist=False,
            collection_class=None,
            default=None,  # because nullable
            default_factory=None,
            doc=None),
    }

    assert generated_fields == expected_fields

    # Compare final values
    assert {
        name: attr.final_value_type
        for name, attr in generated_fields.items()
    } == {
        'id': int,
        'user_id': Optional[str],
        'title': Optional[str],
        'user': Optional[User],
    }

    # Test primary key
    assert sa_model_primary_key_info(Article) == {'id': expected_fields['id']}