def batch_decorator(cls: ModelMetaclass) -> ModelMetaclass: cls.__annotations__ = {arg_name: list for arg_name in model_args_names} cls.__fields__ = { arg_name: ModelField(name=arg_name, type_=list, class_validators=None, model_config=BaseConfig, required=False, field_info=Field(None)) for arg_name in model_args_names } return cls
def build_resp_model(cls): ns = { 'code': Field(cls.CODE, const=True, example=cls.CODE), 'message': Field(cls.MESSAGE, const=True, example=cls.MESSAGE), '__annotations__': { 'code': int, 'message': str, } } data_model = cls.get_data_model() if data_model is not None: if not cls.data_required: data_model = Optional[data_model] # noinspection PyTypeChecker ns['__annotations__']['data'] = data_model name = cls._component_name or cls.__name__ _JsonRpcErrorModel = ModelMetaclass.__new__(ModelMetaclass, '_JsonRpcErrorModel', (BaseModel,), ns) _JsonRpcErrorModel = component_name(name, cls.__module__)(_JsonRpcErrorModel) @component_name(f'_ErrorResponse[{name}]', cls.__module__) class _ErrorResponseModel(BaseModel): jsonrpc: StrictStr = Field('2.0', const=True, example='2.0') id: Union[StrictStr, int] = Field(None, example=0) error: _JsonRpcErrorModel class Config: extra = 'forbid' return _ErrorResponseModel
def __new__(cls, name, bases, namespace, **kwargs): firestore = FirestoreConfig for base in reversed(bases): if ( _DOCUMENT_CLASS_DEFINED and issubclass(base, Document) and base != Document ): firestore = inherit_config(base.__firestore__, firestore) firestore = inherit_config(namespace.get("Firestore"), firestore) annotations = namespace.get("__annotations__", {}) ids = {k: v for k, v in annotations.items() if issubclass(v, FirestoreID)} if ( _DOCUMENT_CLASS_DEFINED and len(ids) != 1 and not hasattr(firestore, "id_attr") ): raise TypeError( f'"{name}" must have exactly one attribute of type FirestoreID.' ) elif len(ids) == 1: id_attr = list(ids.keys())[0] setattr(firestore, "id_attr", id_attr) namespace["__firestore__"] = firestore return ModelMetaclass.__new__(cls, name, bases, namespace, **kwargs)
def _make_output_class(f, json_encoders=None): return_annot = typing.get_type_hints(f).get('return', inspect._empty) if return_annot is inspect._empty: raise TypeError( f"Unable to construct a Task from function '{f.__qualname__}': " "the annotation for the return value is missing. " "This may be a type, or a subclass of TaskOutput.") json_encoders_arg = json_encoders if json_encoders else {} class Config: # NB: MIGHT NOT be used if `f` includes a 'return' annotation json_encoders = { **json_encoders_arg, **smttask_json_encoders, **base.TaskOutput.Config.json_encoders } if lenient_issubclass(return_annot, base.TaskOutput): # Nothing to do Outputs = return_annot # Add the json_encoders to the Output type, but only if they were not # given explicitely in the Output type. if json_encoders: if hasattr(Outputs, 'Config') and not hasattr( Outputs.Config, 'json_encoders'): Outputs.Config.json_encoders = Config.json_encoders elif not hasattr(Outputs, 'Config'): Outputs.Config = Config else: assert isinstance(return_annot, (type, typing._GenericAlias)) # A bare annotation does not define a variable name; we set it to the # empty string (i.e., the variable is only identified by the task name) Outputs = ModelMetaclass(f"{f.__qualname__}.Outputs", (base.TaskOutput, ), { '__annotations__': { "": return_annot }, 'Config': Config }) # Set correct module; workaround for https://bugs.python.org/issue28869 Outputs.__module__ = f.__module__ # update_forward_refs required for 3.9+ style annotations Outputs.update_forward_refs() return Outputs
def _make_input_class(f, json_encoders=None): defaults = {} annotations = {} json_encoders_arg = json_encoders if json_encoders else {} class Config: # Override the lenience of the base TaskInput class and only allow expected arguments extra = 'forbid' json_encoders = { **json_encoders_arg, **smttask_json_encoders, **base.TaskInput.Config.json_encoders } for nm, param in inspect.signature(f).parameters.items(): if param.annotation is inspect._empty: raise TypeError( "Constructing a Task requires that all function arguments " f"be annotated. Offender: argument '{nm}' of '{f.__qualname__}'." ) annotation = param.annotation if isinstance(annotation, str): # HACK to resolve forward refs globalns = sys.modules[f.__module__].__dict__.copy() annotation = evaluate_forwardref(ForwardRef(annotation), globalns=globalns, localns=None) annotations[nm] = Union[base.Task, annotation] if param.default is not inspect._empty: defaults[nm] = param.default Inputs = ModelMetaclass(f"{f.__qualname__}.Inputs", (base.TaskInput, ), { **defaults, 'Config': Config, '__annotations__': annotations }) # Set correct module; workaround for https://bugs.python.org/issue28869 Inputs.__module__ = f.__module__ # update_forward_refs required for 3.9+ style annotations # DEVNOTE: When one inspects Inputs.__fields__, some types may still contain 'ForwardRef' # I don't know why that is, but checking Inputs.__fields__[field name].__args__[ForwardRef index].__forward_evaluated__ should still be True # and [...].__forward_value__ should be the expected type Inputs.update_forward_refs() return Inputs
def make_request_model(name, module, body_params: List[ModelField]): whole_params_list = [ p for p in body_params if isinstance(p.field_info, Params) ] if len(whole_params_list): if len(whole_params_list) > 1: raise RuntimeError(f"Only one 'Params' allowed: " f"params={whole_params_list}") body_params_list = [ p for p in body_params if not isinstance(p.field_info, Params) ] if body_params_list: raise RuntimeError( f"No other params allowed when 'Params' used: " f"params={whole_params_list}, other={body_params_list}") if whole_params_list: assert whole_params_list[0].alias == 'params' params_field = whole_params_list[0] else: _JsonRpcRequestParams = ModelMetaclass.__new__( ModelMetaclass, '_JsonRpcRequestParams', (BaseModel, ), {}) for f in body_params: _JsonRpcRequestParams.__fields__[f.name] = f _JsonRpcRequestParams = component_name(f'_Params[{name}]', module)(_JsonRpcRequestParams) params_field = ModelField( name='params', type_=_JsonRpcRequestParams, class_validators={}, default=None, required=True, model_config=BaseConfig, field_info=Field(...), ) class _Request(BaseModel): jsonrpc: StrictStr = Field('2.0', const=True, example='2.0') id: Union[StrictStr, int] = Field(None, example=0) method: StrictStr = Field(name, const=True, example=name) class Config: extra = 'forbid' _Request.__fields__[params_field.name] = params_field _Request = component_name(f'_Request[{name}]', module)(_Request) return _Request
def __new__(mcs, name, bases, namespace): slots = set(namespace.pop("__slots__", tuple())) for base in bases: if hasattr(base, "__slots__"): slots.update(base.__slots__) if "__dict__" in slots: slots.remove("__dict__") namespace["__slots__"] = tuple(slots) return ModelMetaclass.__new__(mcs, name, bases, namespace)
def _get_model( DataClass: Type[D], excludes=[]) -> Type[Union[BaseModel, Type[D], Type[T]]]: from dataclasses import is_dataclass annotations = (dict( (name, SCHEMAS.get(type, type)) for name, type in DataClass.__annotations__.items() if not excludes or name not in excludes) if hasattr( DataClass, "__annotations__") else {}) for field_name, field_type in annotations.items(): if type(field_type) == GenericAlias and field_type.__mro__[ 0] in [ # type: ignore list, set, ]: gen_type = field_type.__mro__[0] arg_type = field_type.__args__[0] arg_type = SCHEMAS.get(arg_type, arg_type) field_type.__args__[0] annotations[field_name] = GenericAlias( gen_type, (arg_type, )) # type: ignore if is_dataclass(field_type): field = _get_model(field_type) annotations[field_name] = field namespace = { "__annotations__": annotations, "__module__": TargetClass.__module__, "__qualname__": TargetClass.__qualname__, } model = cast( AnyType, ModelMetaclass(TargetClass.__name__, (ModelSchema, ), namespace), ) for field_name, field_type in annotations.items(): member = members.get(field_name) field = model.__dict__["__fields__"].get(field_name) if not field: continue if isinstance(member, (DataClassField, )): setattr(field, "field_info", Field(..., **member.metadata)) elif isinstance(member, (FieldInfo, )): setattr(field, "field_info", member) return model
def build_data_model(cls): if cls.DataModel is not None: return rename_if_scope_child_component(cls, cls.DataModel, 'Data') error_model = cls.get_error_model() if error_model is None: return None errors_annotation = List[error_model] if not cls.errors_required: errors_annotation = Optional[errors_annotation] ns = { '__annotations__': { 'errors': errors_annotation, } } _ErrorData = ModelMetaclass.__new__(ModelMetaclass, '_ErrorData', (BaseModel,), ns) _ErrorData = component_name(f'_ErrorData[{error_model.__name__}]', error_model.__module__)(_ErrorData) return _ErrorData
def expand_doc(klass: ModelMetaclass) -> ModelMetaclass: """Expand pydantic model documentation to enable autodoc.""" docs = ['', '', 'Keyword Args:'] for name, field in klass.__fields__.items(): # type: ignore default_str = '' # if field.default: default_str = '' if field.default: if SecretStr not in field.type_.__mro__: default = field.default if Path in field.type_.__mro__: default = str(Path(default).relative_to(Path(default).parents[2])) if field.name == 'user_klass': default_str = f' [default: :class:`{default.replace("`", "").replace(":", ".")}`]' else: default_str = f' [default: ``{default}``]' else: default_str = ' [default: ``uuid.uuid4()``]' module = field.outer_type_.__module__ if module != 'builtins': if hasattr(field.outer_type_, '__origin__'): type_ = f' ({field.outer_type_.__origin__.__name__}) ' elif not hasattr(field.outer_type_, '__name__'): type_ = '' else: type_ = f' ({module}.{field.outer_type_.__name__}) ' else: type_ = f' ({field.outer_type_.__name__}) ' env_var = '' if 'env' in field.field_info.extra: env_var = f' (Can be set by ``{field.field_info.extra["env"]}`` environment variable)' docs.append(f' {name}{type_}: {field.field_info.description}{default_str}{env_var}') if klass.__doc__ is None: klass.__doc__ = '' klass.__doc__ += '\n'.join(docs) return klass
def __new__(mcs, name, bases, namespace, **kwargs): if bases[0] == BaseModel: return super().__new__(mcs, name, bases, namespace, **kwargs) inherited_columns = {} for base in bases[::-1]: if issubclass(base, OrmModel) and base is not OrmModel: inherited_columns.update( {x.name: x.copy() for x in base.__columns__.values()}) mcs._ensure_proper_init(namespace) table_name = namespace.get('__tablename__', None) or camel_to_snake(name) metadata = namespace.get('__metadata__', None) or FoxOrm.metadata abstract = namespace.get('__abstract__', None) or False new_namespace = {} relation_namespace = {} for k, v in namespace.items(): if k == '__tablename__': continue if isinstance(v, _GenericIterableRelation): relation_namespace[k] = v else: new_namespace[k] = v new_namespace['__annotations__'] = annotations = {} for k, v in namespace.get('__annotations__', {}).items(): if k in relation_namespace: continue annotations[k] = v columns = {} for column_name, namespace_value in new_namespace.items(): if not is_valid_column_name( column_name) or not is_valid_column_value(namespace_value): continue if column_name not in annotations: raise OrmException(f'Unannotated field {column_name}') column, value = construct_column(column_name, annotations[column_name], namespace_value) columns[column.name] = column new_namespace[column_name] = value for column_name, annotation in annotations.items(): if not is_valid_column_name(column_name): continue if column_name not in columns: column, _ = construct_column(column_name, annotation, tuple()) columns[column.name] = column all_columns = inherited_columns.copy() all_columns.update(columns) # Hack for generating FastAPI models if (len(bases) == 1 and issubclass((base := bases[0]), OrmModel) and base is not OrmModel and base.__name__ == name): stack = traceback.extract_stack() if len(stack) >= 3: caller = stack[-3] if caller.name == 'create_cloned_field' and caller.filename.endswith( f'{os.sep}fastapi{os.sep}utils.py'): return ModelMetaclass(name, (BaseModel, ), base.get_namespace())