Ejemplo n.º 1
0
def validate_config(cls_config: Type[BaseODMConfig],
                    cls_name: str) -> Type[BaseODMConfig]:
    """Validate and build the model configuration"""
    for name in dir(cls_config):
        if not is_dunder(name) and name not in ALLOWED_CONFIG_OPTIONS:
            raise ValueError(f"'{cls_name}': 'Config.{name}' is not supported")

    if cls_config is BaseODMConfig:
        bases = (EnforcedPydanticConfig, BaseODMConfig, BaseConfig)
    else:
        bases = (
            EnforcedPydanticConfig,
            cls_config,
            BaseODMConfig,
            BaseConfig,
        )  # type:ignore

    # Merge json_encoders to preserve bson type encoders
    namespace = {
        "json_encoders": {
            **BSON_TYPES_ENCODERS,
            **getattr(cls_config, "json_encoders", {}),
        }
    }
    return type("Config", bases, namespace)
Ejemplo n.º 2
0
    def __validate_cls_namespace__(name: str,
                                   namespace: Dict) -> None:  # noqa C901
        """Validate the class name space in place"""
        annotations = resolve_annotations(namespace.get("__annotations__", {}),
                                          namespace.get("__module__"))
        config = validate_config(namespace.get("Config", BaseODMConfig), name)
        odm_fields: Dict[str, ODMBaseField] = {}
        references: List[str] = []
        bson_serialized_fields: Set[str] = set()
        mutable_fields: Set[str] = set()

        # Make sure all fields are defined with type annotation
        for field_name, value in namespace.items():
            if (should_touch_field(value=value) and not is_dunder(field_name)
                    and field_name not in annotations):
                raise TypeError(
                    f"field {field_name} is defined without type annotation")

        # Validate fields types and substitute bson fields
        for (field_name, field_type) in annotations.items():
            if not is_dunder(field_name) and should_touch_field(
                    type_=field_type):
                substituted_type = validate_type(field_type)
                # Handle BSON serialized fields after substitution to allow some
                # builtin substitution
                bson_serialization_method = getattr(substituted_type,
                                                    "__bson__", None)
                if bson_serialization_method is not None:
                    bson_serialized_fields.add(field_name)
                annotations[field_name] = substituted_type

        # Validate fields
        for (field_name, field_type) in annotations.items():
            value = namespace.get(field_name, Undefined)

            if is_dunder(field_name) or not should_touch_field(
                    value, field_type):
                continue  # pragma: no cover
                # https://github.com/nedbat/coveragepy/issues/198

            if isinstance(value, PDFieldInfo):
                raise TypeError(
                    "please use odmantic.Field instead of pydantic.Field")

            if is_type_mutable(field_type):
                mutable_fields.add(field_name)

            if lenient_issubclass(field_type, EmbeddedModel):
                if isinstance(value, ODMFieldInfo):
                    namespace[field_name] = value.pydantic_field_info
                    key_name = (value.key_name
                                if value.key_name is not None else field_name)
                    primary_field = value.primary_field
                else:
                    key_name = field_name
                    primary_field = False

                odm_fields[field_name] = ODMEmbedded(
                    primary_field=primary_field,
                    model=field_type,
                    key_name=key_name,
                    model_config=config,
                )
            elif lenient_issubclass(field_type, Model):
                if not isinstance(value, ODMReferenceInfo):
                    raise TypeError(
                        f"cannot define a reference {field_name} (in {name}) without"
                        " a Reference assigned to it")
                key_name = value.key_name if value.key_name is not None else field_name
                raise_on_invalid_key_name(key_name)
                odm_fields[field_name] = ODMReference(model=field_type,
                                                      key_name=key_name,
                                                      model_config=config)
                references.append(field_name)
                del namespace[
                    field_name]  # Remove default ODMReferenceInfo value
            else:
                if isinstance(value, ODMFieldInfo):
                    key_name = (value.key_name
                                if value.key_name is not None else field_name)
                    raise_on_invalid_key_name(key_name)
                    odm_fields[field_name] = ODMField(
                        primary_field=value.primary_field,
                        key_name=key_name,
                        model_config=config,
                    )
                    namespace[field_name] = value.pydantic_field_info

                elif value is Undefined:
                    odm_fields[field_name] = ODMField(primary_field=False,
                                                      key_name=field_name,
                                                      model_config=config)

                else:
                    try:
                        parse_obj_as(field_type, value)
                    except ValidationError:
                        raise TypeError(
                            f"Unhandled field definition {name}: {repr(field_type)}"
                            f" = {repr(value)}")
                    odm_fields[field_name] = ODMField(primary_field=False,
                                                      key_name=field_name,
                                                      model_config=config)

        duplicate_key = find_duplicate_key(odm_fields.values())
        if duplicate_key is not None:
            raise TypeError(f"Duplicated key_name: {duplicate_key} in {name}")
        # NOTE: Duplicate key detection make sur that at most one primary key is
        # defined
        namespace["__annotations__"] = annotations
        namespace["__odm_fields__"] = odm_fields
        namespace["__references__"] = tuple(references)
        namespace["__bson_serialized_fields__"] = frozenset(
            bson_serialized_fields)
        namespace["__mutable_fields__"] = frozenset(mutable_fields)
        namespace["Config"] = config
Ejemplo n.º 3
0
    collection: Optional[str] = None
    parse_doc_with_default_factories: bool = False

    # Inherited from pydantic
    title: Optional[str] = None
    json_encoders: Dict[Type[Any], AnyCallable] = {}
    schema_extra: Union[Dict[str, Any], "SchemaExtraCallable"] = {}
    anystr_strip_whitespace: bool = False
    json_loads: Callable[[str], Any] = json.loads
    json_dumps: Callable[..., str] = json.dumps
    arbitrary_types_allowed: bool = False


ALLOWED_CONFIG_OPTIONS = {
    name
    for name in dir(BaseODMConfig) if not is_dunder(name)
}


class EnforcedPydanticConfig:
    """Configuration options enforced to work with Models"""

    validate_all = True
    validate_assignment = True


def validate_config(cls_config: Type[BaseODMConfig],
                    cls_name: str) -> Type[BaseODMConfig]:
    """Validate and build the model configuration"""
    for name in dir(cls_config):
        if not is_dunder(name) and name not in ALLOWED_CONFIG_OPTIONS:
Ejemplo n.º 4
0
    Defines as well the fields allowed to be passed.
    """

    collection: Optional[str] = None
    parse_doc_with_default_factories: bool = False

    # Inherited from pydantic
    title: Optional[str] = None
    json_encoders: Dict[Type[Any], AnyCallable] = {}
    schema_extra: Union[Dict[str, Any], "SchemaExtraCallable"] = {}
    anystr_strip_whitespace: bool = False
    json_loads: Callable[[str], Any] = json.loads
    json_dumps: Callable[..., str] = json.dumps


ALLOWED_CONFIG_OPTIONS = {name for name in dir(BaseODMConfig) if not is_dunder(name)}


class EnforcedPydanticConfig:
    """Configuration options enforced to work with Models"""

    validate_all = True
    validate_assignment = True


def validate_config(
    cls_config: Type[BaseODMConfig], cls_name: str
) -> Type[BaseODMConfig]:
    """Validate and build the model configuration"""
    for name in dir(cls_config):
        if not is_dunder(name) and name not in ALLOWED_CONFIG_OPTIONS: