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)
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
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:
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: