From 940966e95801f3e83568fe236883c922a15f548a Mon Sep 17 00:00:00 2001 From: voidZXL Date: Thu, 24 Oct 2024 11:17:07 +0800 Subject: [PATCH] add warnings settings to control and filter warnings --- utype/decorator.py | 10 ++++++---- utype/parser/cls.py | 9 +++++++-- utype/parser/field.py | 42 ++++++++++++++++++++++++----------------- utype/parser/func.py | 34 +++++++++++++++++++-------------- utype/parser/options.py | 9 +++++---- utype/parser/rule.py | 42 ++++++++++++++++++++++++----------------- utype/settings.py | 40 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 128 insertions(+), 58 deletions(-) create mode 100644 utype/settings.py diff --git a/utype/decorator.py b/utype/decorator.py index cdb2188..8455ffc 100644 --- a/utype/decorator.py +++ b/utype/decorator.py @@ -8,6 +8,7 @@ from .parser.rule import Lax, Rule from .utils import exceptions as exc from .utils.datastructures import Unprovided, unprovided +from .settings import warning_settings T = TypeVar("T") FUNC = TypeVar("FUNC") @@ -34,9 +35,9 @@ def parse( eager: bool = False, ) -> Union[T, Callable[[T], T]]: if ignore_params and ignore_result: - warnings.warn( + warning_settings.warn( f"@utype.parse: you turn off both params and result parse in @parse decorator," - f" which is basically meaningless..." + f" which is basically meaningless...", warning_settings.parse_ignore_both_params_and_result ) def decorator(func: T) -> T: @@ -103,9 +104,10 @@ def decorator(cls: CLS) -> CLS: post_setattr=post_setattr, post_delattr=post_delattr ) elif post_setattr or post_delattr: - warnings.warn( + warning_settings.warn( f"@utype.dataclass received post_delattr / post_setattr " - f'without "set_properties=True", these params won\'t take effect' + f'without "set_properties=True", these params won\'t take effect', + warning_settings.dataclass_setattr_delattr_not_effect ) cls.__parser__ = parser diff --git a/utype/parser/cls.py b/utype/parser/cls.py index 1d2fae9..f73af11 100644 --- a/utype/parser/cls.py +++ b/utype/parser/cls.py @@ -99,8 +99,13 @@ def globals(self) -> dict: current_obj = dic.get(name) if current_obj: if getattr(current_obj, '__qualname__', None) != getattr(self.obj, '__qualname__', None): - warnings.warn(f'Parser object: {self.obj} got conflict object: {current_obj} ' - f'with same name: {repr(name)}, it may affect the ForwardRef resolve') + from ..settings import warning_settings + warning_settings.warn( + f'Parser object: {self.obj} got conflict object: {current_obj} ' + f'with same name: {repr(name)}, it may affect the ForwardRef resolve', + warning_settings.globals_name_conflict + ) + dic[name] = self.obj # !IMPORTANT: we need to override __name__ for current obj # cause in the locals, same name may be the different object, we should be careful about that diff --git a/utype/parser/field.py b/utype/parser/field.py index e6fc6d1..b2c1437 100644 --- a/utype/parser/field.py +++ b/utype/parser/field.py @@ -1,5 +1,4 @@ import inspect -import warnings from collections.abc import Mapping from datetime import date, datetime, time, timedelta, timezone from decimal import Decimal @@ -14,6 +13,7 @@ from ..utils.functional import copy_value, get_name, multi from .options import Options, RuntimeContext from .rule import ConstraintMode, Lax, LogicalType, Rule, resolve_forward_type +from ..settings import warning_settings represent = repr @@ -563,10 +563,11 @@ def setup(self, options: Options): trans = Rule.transformer_cls.resolver_transformer(self.type) if not trans: if options.unresolved_types == options.THROW: - warnings.warn( + warning_settings.warn( f"Field(name={repr(self.name)}) got unresolved type: {self.type}, " f'and Options.unresolved_types == "throw", which will raise error' - f" in the runtime if the input value type does not match" + f" in the runtime if the input value type does not match", + warning_settings.field_unresolved_types_with_throw_options ) else: self.input_transformer = trans @@ -578,10 +579,11 @@ def setup(self, options: Options): trans = Rule.transformer_cls.resolver_transformer(self.output_type) if not trans: if options.unresolved_types == options.THROW: - warnings.warn( + warning_settings.warn( f"Field(name={repr(self.name)}) got unresolved output type: {self.output_type}, " f'and Options.unresolved_types == "throw", which will raise error' - f" in the runtime if the input value type does not match" + f" in the runtime if the input value type does not match", + warning_settings.field_unresolved_types_with_throw_options ) else: self.output_transformer = trans @@ -918,40 +920,46 @@ def check_function(self, func): if self.positional_only: if self.field.alias: - warnings.warn( + warning_settings.warn( f"{func}: Field(name={repr(self.name)}).alias ({repr(self.field.alias)}) " f"has no meanings in positional only params," - f" please consider move it" + f" please consider move it", + warning_settings.field_alias_on_positional_args ) if self.field.alias_from: - warnings.warn( + warning_settings.warn( f"{func}: Field(name={repr(self.name)}).alias_from ({repr(self.field.alias_from)}) " f"has no meanings in positional only params," - f" please consider move it" + f" please consider move it", + warning_settings.field_alias_on_positional_args ) if self.case_insensitive: - warnings.warn( + warning_settings.warn( f"{func}: Field(name={repr(self.name)}).case_insensitive " f"has no meanings in positional only params," - f" please consider move it" + f" please consider move it", + warning_settings.field_case_sensitive_on_positional_args ) if self.no_output: - warnings.warn( + warning_settings.warn( f"{func}: Field(name={repr(self.name)}).no_output has no meanings in function params," - f" please consider move it" + f" please consider move it", + warning_settings.field_invalid_params_in_function ) if self.immutable: - warnings.warn( + warning_settings.warn( f"{func}: Field(name={repr(self.name)}).immutable has no meanings in function params, " - f"please consider move it" + f"please consider move it", + warning_settings.field_invalid_params_in_function ) if self.field.repr: - warnings.warn( + warning_settings.warn( f"{func}: Field(name={repr(self.name)}).repr has no meanings in function params," - f" please consider move it" + f" please consider move it", + warning_settings.field_invalid_params_in_function ) # 1. deal with parse: use context diff --git a/utype/parser/func.py b/utype/parser/func.py index afd57ab..5dfaaab 100644 --- a/utype/parser/func.py +++ b/utype/parser/func.py @@ -1,5 +1,4 @@ import inspect -import warnings from collections.abc import (AsyncGenerator, AsyncIterable, AsyncIterator, Callable, Generator, Iterable, Iterator, Mapping) from functools import wraps @@ -13,6 +12,7 @@ from .field import ParserField from .options import Options, RuntimeContext from .rule import Rule, resolve_forward_type +from ..settings import warning_settings LAMBDA_NAME = (lambda: None).__name__ LOCALS_NAME = "" @@ -256,9 +256,10 @@ def __init__(self, func, options: Options = None, from_class: type = None): f"without declaring the **kwargs variable" ) if self.kw_var and not self.options.addition: - warnings.warn( + warning_settings.warn( f"FunctionParser: {func}, specified **{self.kw_var}" - f" but set addition=False, {self.kw_var} will always be empty" + f" but set addition=False, {self.kw_var} will always be empty", + warning_settings.function_kwargs_With_no_addition, ) if self.options.no_default: @@ -272,12 +273,14 @@ def __init__(self, func, options: Options = None, from_class: type = None): ) if self.options.immutable: - warnings.warn( - f"FunctionParser: {func}, specified immutable=True in Options, which is useless" + warning_settings( + f"FunctionParser: {func}, specified immutable=True in Options, which is useless", + warning_settings.function_invalid_options ) if self.options.secret_names: - warnings.warn( - f"FunctionParser: {func}, specified secret_names in Options, which is useless" + warning_settings.warn( + f"FunctionParser: {func}, specified secret_names in Options, which is useless", + warning_settings.function_invalid_options ) self.position_type = None @@ -327,9 +330,10 @@ def generate_return_types(self): self.generator_return_type, ) = self.return_type.__args__ else: - warnings.warn( + warning_settings.warn( f"Invalid return type annotation: {self.return_annotation} " - f"for generator function, should be Generator[...] / Iterator[...] / Iterable[...]" + f"for generator function, should be Generator[...] / Iterator[...] / Iterable[...]", + warning_settings.function_invalid_return_annotation ) elif self.is_async_generator: if self.return_type.__origin__ in (AsyncIterable, AsyncIterator): @@ -340,10 +344,11 @@ def generate_return_types(self): self.generator_send_type, ) = self.return_type.__args__ else: - warnings.warn( + warning_settings.warn( f"Invalid return type annotation: {self.return_annotation} " f"for async generator function, should be " - f"AsyncGenerator[...] / AsyncIterator[...] / AsyncIterable[...]" + f"AsyncGenerator[...] / AsyncIterator[...] / AsyncIterable[...]", + warning_settings.function_invalid_return_annotation ) @cached_property @@ -401,9 +406,10 @@ def generate_fields(self): else: annotation = param.annotation if is_final(annotation) or is_classvar(annotation): - warnings.warn( + warning_settings.warn( f"{self.obj}: param: {repr(name)} invalid annotation: {annotation}, " - f"this is only for class variables, please use the type directly" + f"this is only for class variables, please use the type directly", + warning_settings.function_invalid_params_annotation ) # args = get_args(annotation) # annotation = args[0] if args else None @@ -494,7 +500,7 @@ def validate_fields(self): if v.kind == v.POSITIONAL_ONLY: raise SyntaxError(msg) else: - warnings.warn(msg) + warning_settings.warn(msg, warning_settings.function_non_default_follows_default_args) else: optional_name = k diff --git a/utype/parser/options.py b/utype/parser/options.py index 7e84f36..5d16030 100644 --- a/utype/parser/options.py +++ b/utype/parser/options.py @@ -1,13 +1,12 @@ import inspect -import warnings from typing import Any, Callable, List, Optional, Set, Type, Union from ..utils import exceptions as exc -# from ..utils.base import ParamsCollector from ..utils.compat import Literal from ..utils.datastructures import unprovided from ..utils.functional import multi from ..utils.transform import TypeTransformer +from ..settings import warning_settings DEFAULT_SECRET_NAMES = ( "password", @@ -175,8 +174,9 @@ def __init__( if not collect_errors: if max_errors: - warnings.warn( - f"Options with max_errors: {max_errors} should turn on collect_errors=True" + warning_settings.warn( + f"Options with max_errors: {max_errors} should turn on collect_errors=True", + warning_settings.options_max_errors_with_no_collect_errors ) max_errors = None @@ -477,5 +477,6 @@ def handle_error( raise exc.CollectedParseError(errors=errors) def collect_waring(self, warning, category=None): + import warnings warnings.warn(warning, category=category) self.warnings.append(warning) diff --git a/utype/parser/rule.py b/utype/parser/rule.py index 5c9eeda..c6015b3 100644 --- a/utype/parser/rule.py +++ b/utype/parser/rule.py @@ -1,6 +1,5 @@ import re import typing -import warnings from collections import deque from decimal import Decimal from enum import Enum, EnumMeta @@ -14,6 +13,7 @@ from ..utils.datastructures import unprovided from ..utils.functional import multi, pop from ..utils.transform import TypeTransformer +from ..settings import warning_settings from .options import RuntimeContext T = typing.TypeVar("T") @@ -535,15 +535,17 @@ def valid_length(self, bounds: dict): if not hasattr(self.origin_type, "__len__"): # just warning here, we will coerce to str in runtime if issubclass(self.origin_type, (int, float, Decimal)): - warnings.warn( + warning_settings.warn( f"Rule specify length constraints for type: {self.origin_type} " f"that does not support length, we recommend to use " - f'"max_digits" and "round" for number types' + f'"max_digits" and "round" for number types', + warning_settings.rule_length_constraints_on_unsupported_types ) else: - warnings.warn( + warning_settings.warn( f"Rule specify length constraints for type: {self.origin_type} " - f"that does not support length, value will be convert to str to validate length" + f"that does not support length, value will be convert to str to validate length", + warning_settings.rule_length_constraints_on_unsupported_types ) def valid_bounds(self, bounds: dict): @@ -1188,9 +1190,10 @@ def __init_subclass__(cls, **kwargs): cls.__origin__ ) if not cls.__origin_transformer__: - warnings.warn( + warning_settings.warn( f"{cls}: origin type: {cls.__origin__} got no transformer resolved, " - f"will just pass {cls.__origin__}(data) at runtime" + f"will just pass {cls.__origin__}(data) at runtime", + warning_settings.rule_no_origin_transformer ) if cls.__args__: @@ -1221,9 +1224,10 @@ def _cannot_getitem(*_args): cls.__origin__, NONE_ARG_ALLOWED_TYPES ): continue - warnings.warn( + warning_settings( f"None arg: {arg} detected where origin type: {cls.__origin__} is not in " - f"{NONE_ARG_ALLOWED_TYPES}" + f"{NONE_ARG_ALLOWED_TYPES}", + warning_settings.rule_none_arg_in_unsupported_origin ) continue @@ -1231,18 +1235,20 @@ def _cannot_getitem(*_args): raise TypeError(f"Invalid arg: {arg}, must be a class") transformer = cls.transformer_cls.resolver_transformer(arg) if not transformer: - warnings.warn( + warning_settings.warn( f"{cls}: arg type: {arg} got no transformer resolved, " - f"will just pass {arg}(data) at runtime" + f"will just pass {arg}(data) at runtime", + warning_settings.rule_no_arg_transformer ) arg_transformers.append(transformer) cls.__arg_transformers__ = tuple(arg_transformers) cls.__args_parser__ = cls.resolve_args_parser() if not cls.__args_parser__: - warnings.warn( + warning_settings.warn( f"{cls}: type: {cls.__origin__} with __args__ cannot resolve an args parser, " - f"you should inherit resolve_args_parser and specify yourself" + f"you should inherit resolve_args_parser and specify yourself", + warning_settings.rule_none_arg_in_unsupported_origin ) else: if class_getitem and not cls.__dict__.get("__class_getitem__"): @@ -1278,9 +1284,10 @@ def annotate( if type_ == Any: if args_: - warnings.warn(f"Any type cannot specify args: {args_}") + warning_settings.warn(f"Any type cannot specify args: {args_}", warning_settings.rule_args_in_any) if constraints: - warnings.warn(f"Any type cannot specify constraints: {constraints}") + warning_settings.warn(f"Any type cannot specify constraints: {constraints}", + warning_settings.rule_args_in_any) return Rule elif type_ == Literal: @@ -1805,9 +1812,10 @@ def resolve_forward_refs(cls): resolved = True transformer = cls.transformer_cls.resolver_transformer(arg) if not transformer: - warnings.warn( + warning_settings.warn( f"{cls}: arg type: {arg} got no transformer resolved, " - f"will just pass {arg}(data) at runtime" + f"will just pass {arg}(data) at runtime", + warning_settings.rule_no_arg_transformer ) trans = transformer or trans args.append(arg) diff --git a/utype/settings.py b/utype/settings.py new file mode 100644 index 0000000..0dcc173 --- /dev/null +++ b/utype/settings.py @@ -0,0 +1,40 @@ +import warnings + + +class WarningSettings: + disabled: bool = False + parse_ignore_both_params_and_result: bool = True + dataclass_setattr_delattr_not_effect: bool = True + globals_name_conflict: bool = True + field_unresolved_types_with_throw_options: bool = True + field_alias_on_positional_args: bool = True + field_case_sensitive_on_positional_args: bool = True + field_invalid_params_in_function: bool = True + + function_kwargs_With_no_addition: bool = True + function_invalid_options: bool = True + function_invalid_return_annotation: bool = True + function_invalid_params_annotation: bool = True + function_non_default_follows_default_args: bool = True + + options_max_errors_with_no_collect_errors: bool = True + + rule_length_constraints_on_unsupported_types: bool = True + rule_no_origin_transformer: bool = True + rule_no_arg_transformer: bool = True + rule_arg_parser_unresolved: bool = True + rule_none_arg_in_unsupported_origin: bool = True + rule_args_in_any: bool = True + + def warn(self, message: str, type: str = None): + if self.disabled: + return + if type is False: + return + if isinstance(type, str): + if not getattr(self, type, None): + return + warnings.warn(message) + + +warning_settings = WarningSettings()