chore: enable mypy strict mode (#257)

Signed-off-by: gruebel <anton.gruebel@gmail.com>
Co-authored-by: Federico Bond <federicobond@gmail.com>
This commit is contained in:
Anton Grübel 2024-01-09 00:09:53 +01:00 committed by GitHub
parent 30f4e692d8
commit af9d3da336
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 70 additions and 39 deletions

View File

@ -1,7 +1,8 @@
import sys import sys
if sys.version_info >= (3, 11): if sys.version_info >= (3, 11):
from enum import StrEnum # re-export needed for type checking
from enum import StrEnum as StrEnum # noqa: PLC0414
else: else:
from enum import Enum from enum import Enum

View File

@ -21,7 +21,7 @@ def get_client(
return OpenFeatureClient(name=name, version=version, provider=_provider) return OpenFeatureClient(name=name, version=version, provider=_provider)
def set_provider(provider: AbstractProvider): def set_provider(provider: AbstractProvider) -> None:
global _provider global _provider
if provider is None: if provider is None:
raise GeneralError(error_message="No provider") raise GeneralError(error_message="No provider")
@ -46,19 +46,19 @@ def get_evaluation_context() -> EvaluationContext:
return _evaluation_context return _evaluation_context
def set_evaluation_context(evaluation_context: EvaluationContext): def set_evaluation_context(evaluation_context: EvaluationContext) -> None:
global _evaluation_context global _evaluation_context
if evaluation_context is None: if evaluation_context is None:
raise GeneralError(error_message="No api level evaluation context") raise GeneralError(error_message="No api level evaluation context")
_evaluation_context = evaluation_context _evaluation_context = evaluation_context
def add_hooks(hooks: typing.List[Hook]): def add_hooks(hooks: typing.List[Hook]) -> None:
global _hooks global _hooks
_hooks = _hooks + hooks _hooks = _hooks + hooks
def clear_hooks(): def clear_hooks() -> None:
global _hooks global _hooks
_hooks = [] _hooks = []
@ -68,5 +68,5 @@ def get_hooks() -> typing.List[Hook]:
return _hooks return _hooks
def shutdown(): def shutdown() -> None:
_provider.shutdown() _provider.shutdown()

View File

@ -45,11 +45,21 @@ GetDetailCallable = typing.Union[
FlagResolutionDetails[typing.Union[dict, list]], FlagResolutionDetails[typing.Union[dict, list]],
], ],
] ]
TypeMap = typing.Dict[
FlagType,
typing.Union[
typing.Type[bool],
typing.Type[int],
typing.Type[float],
typing.Type[str],
typing.Tuple[typing.Type[dict], typing.Type[list]],
],
]
@dataclass @dataclass
class ClientMetadata: class ClientMetadata:
name: str name: typing.Optional[str]
class OpenFeatureClient: class OpenFeatureClient:
@ -60,17 +70,17 @@ class OpenFeatureClient:
provider: AbstractProvider, provider: AbstractProvider,
context: typing.Optional[EvaluationContext] = None, context: typing.Optional[EvaluationContext] = None,
hooks: typing.Optional[typing.List[Hook]] = None, hooks: typing.Optional[typing.List[Hook]] = None,
): ) -> None:
self.name = name self.name = name
self.version = version self.version = version
self.context = context or EvaluationContext() self.context = context or EvaluationContext()
self.hooks = hooks or [] self.hooks = hooks or []
self.provider = provider self.provider = provider
def get_metadata(self): def get_metadata(self) -> ClientMetadata:
return ClientMetadata(name=self.name) return ClientMetadata(name=self.name)
def add_hooks(self, hooks: typing.List[Hook]): def add_hooks(self, hooks: typing.List[Hook]) -> None:
self.hooks = self.hooks + hooks self.hooks = self.hooks + hooks
def get_boolean_value( def get_boolean_value(
@ -93,7 +103,7 @@ class OpenFeatureClient:
default_value: bool, default_value: bool,
evaluation_context: typing.Optional[EvaluationContext] = None, evaluation_context: typing.Optional[EvaluationContext] = None,
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None, flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
) -> FlagEvaluationDetails: ) -> FlagEvaluationDetails[bool]:
return self.evaluate_flag_details( return self.evaluate_flag_details(
FlagType.BOOLEAN, FlagType.BOOLEAN,
flag_key, flag_key,
@ -122,7 +132,7 @@ class OpenFeatureClient:
default_value: str, default_value: str,
evaluation_context: typing.Optional[EvaluationContext] = None, evaluation_context: typing.Optional[EvaluationContext] = None,
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None, flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
) -> FlagEvaluationDetails: ) -> FlagEvaluationDetails[str]:
return self.evaluate_flag_details( return self.evaluate_flag_details(
FlagType.STRING, FlagType.STRING,
flag_key, flag_key,
@ -151,7 +161,7 @@ class OpenFeatureClient:
default_value: int, default_value: int,
evaluation_context: typing.Optional[EvaluationContext] = None, evaluation_context: typing.Optional[EvaluationContext] = None,
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None, flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
) -> FlagEvaluationDetails: ) -> FlagEvaluationDetails[int]:
return self.evaluate_flag_details( return self.evaluate_flag_details(
FlagType.INTEGER, FlagType.INTEGER,
flag_key, flag_key,
@ -180,7 +190,7 @@ class OpenFeatureClient:
default_value: float, default_value: float,
evaluation_context: typing.Optional[EvaluationContext] = None, evaluation_context: typing.Optional[EvaluationContext] = None,
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None, flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
) -> FlagEvaluationDetails: ) -> FlagEvaluationDetails[float]:
return self.evaluate_flag_details( return self.evaluate_flag_details(
FlagType.FLOAT, FlagType.FLOAT,
flag_key, flag_key,
@ -195,7 +205,7 @@ class OpenFeatureClient:
default_value: typing.Union[dict, list], default_value: typing.Union[dict, list],
evaluation_context: typing.Optional[EvaluationContext] = None, evaluation_context: typing.Optional[EvaluationContext] = None,
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None, flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
) -> dict: ) -> typing.Union[dict, list]:
return self.get_object_details( return self.get_object_details(
flag_key, flag_key,
default_value, default_value,
@ -209,7 +219,7 @@ class OpenFeatureClient:
default_value: typing.Union[dict, list], default_value: typing.Union[dict, list],
evaluation_context: typing.Optional[EvaluationContext] = None, evaluation_context: typing.Optional[EvaluationContext] = None,
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None, flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
) -> FlagEvaluationDetails: ) -> FlagEvaluationDetails[typing.Union[dict, list]]:
return self.evaluate_flag_details( return self.evaluate_flag_details(
FlagType.OBJECT, FlagType.OBJECT,
flag_key, flag_key,
@ -225,7 +235,7 @@ class OpenFeatureClient:
default_value: typing.Any, default_value: typing.Any,
evaluation_context: typing.Optional[EvaluationContext] = None, evaluation_context: typing.Optional[EvaluationContext] = None,
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None, flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
) -> FlagEvaluationDetails: ) -> FlagEvaluationDetails[typing.Any]:
""" """
Evaluate the flag requested by the user from the clients provider. Evaluate the flag requested by the user from the clients provider.
@ -335,7 +345,7 @@ class OpenFeatureClient:
flag_key: str, flag_key: str,
default_value: typing.Any, default_value: typing.Any,
evaluation_context: typing.Optional[EvaluationContext] = None, evaluation_context: typing.Optional[EvaluationContext] = None,
) -> FlagEvaluationDetails: ) -> FlagEvaluationDetails[typing.Any]:
""" """
Encapsulated method to create a FlagEvaluationDetail from a specific provider. Encapsulated method to create a FlagEvaluationDetail from a specific provider.
@ -384,8 +394,8 @@ class OpenFeatureClient:
) )
def _typecheck_flag_value(value, flag_type): def _typecheck_flag_value(value: typing.Any, flag_type: FlagType) -> None:
type_map = { type_map: TypeMap = {
FlagType.BOOLEAN: bool, FlagType.BOOLEAN: bool,
FlagType.STRING: str, FlagType.STRING: str,
FlagType.OBJECT: (dict, list), FlagType.OBJECT: (dict, list),

View File

@ -46,8 +46,11 @@ class Hook:
return None return None
def after( def after(
self, hook_context: HookContext, details: FlagEvaluationDetails, hints: dict self,
): hook_context: HookContext,
details: FlagEvaluationDetails[typing.Any],
hints: dict,
) -> None:
""" """
Runs after a flag is resolved. Runs after a flag is resolved.
@ -58,7 +61,9 @@ class Hook:
""" """
pass pass
def error(self, hook_context: HookContext, exception: Exception, hints: dict): def error(
self, hook_context: HookContext, exception: Exception, hints: dict
) -> None:
""" """
Run when evaluation encounters an error. Errors thrown will be swallowed. Run when evaluation encounters an error. Errors thrown will be swallowed.
@ -68,7 +73,7 @@ class Hook:
""" """
pass pass
def finally_after(self, hook_context: HookContext, hints: dict): def finally_after(self, hook_context: HookContext, hints: dict) -> None:
""" """
Run after flag evaluation, including any error processing. Run after flag evaluation, including any error processing.
This will always run. Errors will be swallowed. This will always run. Errors will be swallowed.

View File

@ -13,7 +13,7 @@ def error_hooks(
exception: Exception, exception: Exception,
hooks: typing.List[Hook], hooks: typing.List[Hook],
hints: typing.Optional[typing.Mapping] = None, hints: typing.Optional[typing.Mapping] = None,
): ) -> None:
kwargs = {"hook_context": hook_context, "exception": exception, "hints": hints} kwargs = {"hook_context": hook_context, "exception": exception, "hints": hints}
_execute_hooks( _execute_hooks(
flag_type=flag_type, hooks=hooks, hook_method=HookType.ERROR, **kwargs flag_type=flag_type, hooks=hooks, hook_method=HookType.ERROR, **kwargs
@ -25,7 +25,7 @@ def after_all_hooks(
hook_context: HookContext, hook_context: HookContext,
hooks: typing.List[Hook], hooks: typing.List[Hook],
hints: typing.Optional[typing.Mapping] = None, hints: typing.Optional[typing.Mapping] = None,
): ) -> None:
kwargs = {"hook_context": hook_context, "hints": hints} kwargs = {"hook_context": hook_context, "hints": hints}
_execute_hooks( _execute_hooks(
flag_type=flag_type, hooks=hooks, hook_method=HookType.FINALLY_AFTER, **kwargs flag_type=flag_type, hooks=hooks, hook_method=HookType.FINALLY_AFTER, **kwargs
@ -35,10 +35,10 @@ def after_all_hooks(
def after_hooks( def after_hooks(
flag_type: FlagType, flag_type: FlagType,
hook_context: HookContext, hook_context: HookContext,
details: FlagEvaluationDetails, details: FlagEvaluationDetails[typing.Any],
hooks: typing.List[Hook], hooks: typing.List[Hook],
hints: typing.Optional[typing.Mapping] = None, hints: typing.Optional[typing.Mapping] = None,
): ) -> None:
kwargs = {"hook_context": hook_context, "details": details, "hints": hints} kwargs = {"hook_context": hook_context, "details": details, "hints": hints}
_execute_hooks_unchecked( _execute_hooks_unchecked(
flag_type=flag_type, hooks=hooks, hook_method=HookType.AFTER, **kwargs flag_type=flag_type, hooks=hooks, hook_method=HookType.AFTER, **kwargs
@ -55,7 +55,7 @@ def before_hooks(
executed_hooks = _execute_hooks_unchecked( executed_hooks = _execute_hooks_unchecked(
flag_type=flag_type, hooks=hooks, hook_method=HookType.BEFORE, **kwargs flag_type=flag_type, hooks=hooks, hook_method=HookType.BEFORE, **kwargs
) )
filtered_hooks = list(filter(lambda hook: hook is not None, executed_hooks)) filtered_hooks = [result for result in executed_hooks if result is not None]
if filtered_hooks: if filtered_hooks:
return reduce(lambda a, b: a.merge(b), filtered_hooks) return reduce(lambda a, b: a.merge(b), filtered_hooks)
@ -64,7 +64,10 @@ def before_hooks(
def _execute_hooks( def _execute_hooks(
flag_type: FlagType, hooks: typing.List[Hook], hook_method: HookType, **kwargs flag_type: FlagType,
hooks: typing.List[Hook],
hook_method: HookType,
**kwargs: typing.Any,
) -> list: ) -> list:
""" """
Run multiple hooks of any hook type. All of these hooks will be run through an Run multiple hooks of any hook type. All of these hooks will be run through an
@ -84,8 +87,11 @@ def _execute_hooks(
def _execute_hooks_unchecked( def _execute_hooks_unchecked(
flag_type: FlagType, hooks, hook_method: HookType, **kwargs flag_type: FlagType,
) -> list: hooks: typing.List[Hook],
hook_method: HookType,
**kwargs: typing.Any,
) -> typing.List[typing.Optional[EvaluationContext]]:
""" """
Execute a single hook without checking whether an exception is thrown. This is Execute a single hook without checking whether an exception is thrown. This is
used in the before and after hooks since any exception will be caught in the used in the before and after hooks since any exception will be caught in the
@ -104,7 +110,9 @@ def _execute_hooks_unchecked(
] ]
def _execute_hook_checked(hook: Hook, hook_method: HookType, **kwargs): def _execute_hook_checked(
hook: Hook, hook_method: HookType, **kwargs: typing.Any
) -> typing.Optional[EvaluationContext]:
""" """
Try and run a single hook and catch any exception thrown. This is used in the Try and run a single hook and catch any exception thrown. This is used in the
after all and error hooks since any error thrown at this point needs to be caught. after all and error hooks since any error thrown at this point needs to be caught.
@ -115,6 +123,10 @@ def _execute_hook_checked(hook: Hook, hook_method: HookType, **kwargs):
:return: the result of the hook method :return: the result of the hook method
""" """
try: try:
return getattr(hook, hook_method.value)(**kwargs) return typing.cast(
"typing.Optional[EvaluationContext]",
getattr(hook, hook_method.value)(**kwargs),
)
except Exception: # pragma: no cover except Exception: # pragma: no cover
logging.error(f"Exception when running {hook_method.value} hooks") logging.error(f"Exception when running {hook_method.value} hooks")
return None

View File

@ -32,7 +32,7 @@ class InMemoryFlag(typing.Generic[T_co]):
state: State = State.ENABLED state: State = State.ENABLED
context_evaluator: typing.Optional[ context_evaluator: typing.Optional[
typing.Callable[ typing.Callable[
["InMemoryFlag", EvaluationContext], FlagResolutionDetails[T_co] ["InMemoryFlag[T_co]", EvaluationContext], FlagResolutionDetails[T_co]
] ]
] = None ] = None
@ -52,7 +52,7 @@ class InMemoryFlag(typing.Generic[T_co]):
) )
FlagStorage = typing.Dict[str, InMemoryFlag] FlagStorage = typing.Dict[str, InMemoryFlag[typing.Any]]
V = typing.TypeVar("V") V = typing.TypeVar("V")
@ -60,7 +60,7 @@ V = typing.TypeVar("V")
class InMemoryProvider(AbstractProvider): class InMemoryProvider(AbstractProvider):
_flags: FlagStorage _flags: FlagStorage
def __init__(self, flags: FlagStorage): def __init__(self, flags: FlagStorage) -> None:
self._flags = flags.copy() self._flags = flags.copy()
def get_metadata(self) -> Metadata: def get_metadata(self) -> Metadata:

View File

@ -8,10 +8,10 @@ from openfeature.provider.metadata import Metadata
class AbstractProvider: class AbstractProvider:
def initialize(self, evaluation_context: EvaluationContext): def initialize(self, evaluation_context: EvaluationContext) -> None:
pass pass
def shutdown(self): def shutdown(self) -> None:
pass pass
@abstractmethod @abstractmethod

View File

@ -29,6 +29,9 @@ Homepage = "https://github.com/open-feature/python-sdk"
files = "openfeature" files = "openfeature"
namespace_packages = true namespace_packages = true
explicit_package_bases = true explicit_package_bases = true
pretty = true
strict = true
disallow_any_generics = false
[tool.ruff] [tool.ruff]
exclude = [ exclude = [