From fdc61a337cbc234cca4d9479f9c26785c14f36b9 Mon Sep 17 00:00:00 2001 From: BobTheBuidler <70677534+BobTheBuidler@users.noreply.github.com> Date: Wed, 20 Nov 2024 15:54:39 -0400 Subject: [PATCH] feat: cythonize validate_flag_value, refactor (#399) * feat: optimize ASyncGenericBase with cython * chore: `black .` * feat: cythonize validate_flag_value, refactor out cnegate_if_necessary, is_sync_c * fix: AttributeError * chore: `black .` * fix: AttributeError * fix: circular import * fix: missing import * chore: `black .` * fix: broken import * fix: type error --------- Co-authored-by: github-actions[bot] --- a_sync/a_sync/_flags.pxd | 2 +- a_sync/a_sync/_flags.pyx | 76 ++++---------------------------------- a_sync/a_sync/_kwargs.pxd | 2 +- a_sync/a_sync/_kwargs.pyi | 26 ------------- a_sync/a_sync/_kwargs.pyx | 27 +++----------- a_sync/a_sync/abstract.pyi | 18 --------- a_sync/a_sync/abstract.pyx | 15 ++++---- a_sync/a_sync/base.pyi | 5 +-- a_sync/a_sync/base.pyx | 10 ++--- a_sync/a_sync/decorator.py | 2 +- a_sync/a_sync/flags.py | 75 +++++++++++++++++++++++++++++++++++++ a_sync/a_sync/function.pyx | 5 +-- a_sync/a_sync/method.pyx | 2 +- a_sync/exceptions.py | 2 +- 14 files changed, 108 insertions(+), 159 deletions(-) create mode 100644 a_sync/a_sync/flags.py diff --git a/a_sync/a_sync/_flags.pxd b/a_sync/a_sync/_flags.pxd index 5913e49e..d8d74588 100644 --- a/a_sync/a_sync/_flags.pxd +++ b/a_sync/a_sync/_flags.pxd @@ -1 +1 @@ -cdef bint cnegate_if_necessary(str flag, object flag_value) \ No newline at end of file +cdef bint negate_if_necessary(str flag, object flag_value) \ No newline at end of file diff --git a/a_sync/a_sync/_flags.pyx b/a_sync/a_sync/_flags.pyx index fa57ee29..811dae53 100644 --- a/a_sync/a_sync/_flags.pyx +++ b/a_sync/a_sync/_flags.pyx @@ -13,70 +13,11 @@ You can use any of the provided flags, whichever makes the most sense for your u :obj:`VIABLE_FLAGS`: Set of all valid flags, combining both synchronous and asynchronous indicators. """ -from typing import Any, Set - from a_sync import exceptions - -AFFIRMATIVE_FLAGS: Set[str] = {"sync"} -"""Set of flags indicating synchronous behavior. - -This set currently contains only the flag "sync", which is used to denote -synchronous operations within the ez-a-sync library. - -Examples: - >>> 'sync' in AFFIRMATIVE_FLAGS - True - - >>> 'async' in AFFIRMATIVE_FLAGS - False - -See Also: - :data:`NEGATIVE_FLAGS`: Flags indicating asynchronous behavior. - :data:`VIABLE_FLAGS`: All valid flags, combining both sync and async indicators. -""" - -NEGATIVE_FLAGS: Set[str] = {"asynchronous"} -"""Set of flags indicating asynchronous behavior. - -This set currently contains only the flag "asynchronous", which is used to denote -asynchronous operations within the ez-a-sync library. - -Examples: - >>> 'asynchronous' in NEGATIVE_FLAGS - True - - >>> 'sync' in NEGATIVE_FLAGS - False - -See Also: - :data:`AFFIRMATIVE_FLAGS`: Flags indicating synchronous behavior. - :data:`VIABLE_FLAGS`: All valid flags, combining both sync and async indicators. -""" - -VIABLE_FLAGS: Set[str] = AFFIRMATIVE_FLAGS | NEGATIVE_FLAGS -"""Set of all valid flags, combining both synchronous and asynchronous indicators. - -The ez-a-sync library uses these flags to indicate whether objects or function -calls will be synchronous or asynchronous. You can use any of the provided flags, -whichever makes the most sense for your use case. - -Examples: - >>> 'sync' in VIABLE_FLAGS - True - - >>> 'asynchronous' in VIABLE_FLAGS - True - - >>> 'invalid' in VIABLE_FLAGS - False - -See Also: - :data:`AFFIRMATIVE_FLAGS`: Flags indicating synchronous behavior. - :data:`NEGATIVE_FLAGS`: Flags indicating asynchronous behavior. -""" +from a_sync.a_sync.flags import AFFIRMATIVE_FLAGS, NEGATIVE_FLAGS -def negate_if_necessary(flag: str, flag_value: bool) -> bool: +cdef bint negate_if_necessary(str flag, object flag_value): """Negate the flag value if necessary based on the flag type. This function checks if the provided flag is in the set of affirmative or negative flags @@ -102,19 +43,16 @@ def negate_if_necessary(flag: str, flag_value: bool) -> bool: See Also: - :func:`validate_flag_value`: Validates that the flag value is a boolean. """ - return cnegate_if_necessary(flag, flag_value) - - -cdef bint cnegate_if_necessary(str flag, object flag_value): - cdef bint value = validate_flag_value(flag, flag_value) + if not isinstance(flag_value, bool): + raise exceptions.InvalidFlagValue(flag, flag_value) if flag in AFFIRMATIVE_FLAGS: - return value + return flag_value elif flag in NEGATIVE_FLAGS: - return not value + return not flag_value raise exceptions.InvalidFlag(flag) -def validate_flag_value(flag: str, flag_value: Any) -> bool: +cdef bint validate_flag_value(str flag, object flag_value): """ Validate that the flag value is a boolean. diff --git a/a_sync/a_sync/_kwargs.pxd b/a_sync/a_sync/_kwargs.pxd index b3c3f478..34490676 100644 --- a/a_sync/a_sync/_kwargs.pxd +++ b/a_sync/a_sync/_kwargs.pxd @@ -1,2 +1,2 @@ cdef object get_flag_name_c(dict kwargs) -cdef bint is_sync_c(str flag, dict kwargs, bint pop_flag) \ No newline at end of file +cdef bint is_sync(str flag, dict kwargs, bint pop_flag) \ No newline at end of file diff --git a/a_sync/a_sync/_kwargs.pyi b/a_sync/a_sync/_kwargs.pyi index 8756c453..70e8364c 100644 --- a/a_sync/a_sync/_kwargs.pyi +++ b/a_sync/a_sync/_kwargs.pyi @@ -23,30 +23,4 @@ def get_flag_name(kwargs: dict) -> Optional[str]: >>> get_flag_name({}) None - - See Also: - :func:`is_sync`: Determines if the operation should be synchronous based on the flag value. - """ - -def is_sync(flag: str, kwargs: dict, pop_flag: bool = False) -> bool: - """ - Determine if the operation should be synchronous based on the flag value. - - Args: - flag (str): The name of the flag to check. - kwargs (dict): A dictionary of keyword arguments. - pop_flag (bool, optional): Whether to remove the flag from kwargs. Defaults to False. - - Examples: - >>> is_sync('sync', {'sync': True}) - True - - >>> is_sync('asynchronous', {'asynchronous': False}) - False - - >>> is_sync('sync', {'sync': True}, pop_flag=True) - True - - See Also: - :func:`get_flag_name`: Retrieves the name of the flag present in the kwargs. """ diff --git a/a_sync/a_sync/_kwargs.pyx b/a_sync/a_sync/_kwargs.pyx index 2cefb7d9..61d0e811 100644 --- a/a_sync/a_sync/_kwargs.pyx +++ b/a_sync/a_sync/_kwargs.pyx @@ -6,8 +6,8 @@ from typing import Optional from libc.stdint cimport uint8_t from a_sync import exceptions -from a_sync.a_sync._flags import VIABLE_FLAGS -from a_sync.a_sync._flags cimport cnegate_if_necessary +from a_sync.a_sync._flags cimport negate_if_necessary +from a_sync.a_sync.flags import VIABLE_FLAGS def get_flag_name(kwargs: dict) -> Optional[str]: @@ -33,12 +33,10 @@ def get_flag_name(kwargs: dict) -> Optional[str]: >>> get_flag_name({}) None - - See Also: - :func:`is_sync`: Determines if the operation should be synchronous based on the flag value. """ return get_flag_name_c(kwargs) + cdef object get_flag_name_c(dict kwargs): cdef list present_flags = [flag for flag in VIABLE_FLAGS if flag in kwargs] cdef uint8_t flags_count = len(present_flags) @@ -49,7 +47,7 @@ cdef object get_flag_name_c(dict kwargs): raise exceptions.TooManyFlags("kwargs", present_flags) -def is_sync(flag: str, kwargs: dict, pop_flag: bool = False) -> bool: +cdef bint is_sync(str flag, dict kwargs, bint pop_flag): """ Determine if the operation should be synchronous based on the flag value. @@ -58,23 +56,10 @@ def is_sync(flag: str, kwargs: dict, pop_flag: bool = False) -> bool: kwargs (dict): A dictionary of keyword arguments. pop_flag (bool, optional): Whether to remove the flag from kwargs. Defaults to False. - Examples: - >>> is_sync('sync', {'sync': True}) - True - - >>> is_sync('asynchronous', {'asynchronous': False}) - False - - >>> is_sync('sync', {'sync': True}, pop_flag=True) - True - See Also: :func:`get_flag_name`: Retrieves the name of the flag present in the kwargs. """ - return is_sync_c(flag, kwargs, pop_flag) - -cdef bint is_sync_c(str flag, dict kwargs, bint pop_flag): if pop_flag: - return cnegate_if_necessary(flag, kwargs.pop(flag)) + return negate_if_necessary(flag, kwargs.pop(flag)) else: - return cnegate_if_necessary(flag, kwargs[flag]) \ No newline at end of file + return negate_if_necessary(flag, kwargs[flag]) \ No newline at end of file diff --git a/a_sync/a_sync/abstract.pyi b/a_sync/a_sync/abstract.pyi index bdb48a0f..7aab3d1a 100644 --- a/a_sync/a_sync/abstract.pyi +++ b/a_sync/a_sync/abstract.pyi @@ -73,24 +73,6 @@ class ASyncABC(metaclass=ASyncMeta): True """ - def __a_sync_should_await_from_kwargs__(self, kwargs: dict) -> bool: - """Determines execution mode from keyword arguments. - - This method can be overridden to customize how flags are extracted - from keyword arguments. - - Args: - kwargs: A dictionary of keyword arguments to check for flags. - - Raises: - NoFlagsFound: If no valid flags are found in the keyword arguments. - - Examples: - >>> instance = MyASyncClass() - >>> instance.__a_sync_should_await_from_kwargs__({'sync': False}) - True - """ - @classmethod def __a_sync_instance_will_be_sync__(cls, args: tuple, kwargs: dict) -> bool: """Determines if a new instance will be synchronous. diff --git a/a_sync/a_sync/abstract.pyx b/a_sync/a_sync/abstract.pyx index bab3abcc..95e38df6 100644 --- a/a_sync/a_sync/abstract.pyx +++ b/a_sync/a_sync/abstract.pyx @@ -13,12 +13,11 @@ import abc import logging from typing import Dict, Any, Tuple -from a_sync import exceptions from a_sync._typing import * -from a_sync.a_sync import modifiers -from a_sync.a_sync cimport _flags, _kwargs +from a_sync.a_sync cimport _kwargs +from a_sync.a_sync._flags cimport negate_if_necessary from a_sync.a_sync._meta import ASyncMeta -from a_sync.exceptions import NoFlagsFound + logger = logging.getLogger(__name__) @@ -89,7 +88,7 @@ class ASyncABC(metaclass=ASyncMeta): cdef object flag if flag := _kwargs.get_flag_name_c(kwargs): - return _kwargs.is_sync_c(flag, kwargs, pop_flag=True) + return _kwargs.is_sync(flag, kwargs, pop_flag=True) cdef ShouldAwaitCache cache @@ -102,7 +101,7 @@ class ASyncABC(metaclass=ASyncMeta): ) if not cache.is_cached: - cache.value = _flags.cnegate_if_necessary( + cache.value = negate_if_necessary( self.__a_sync_flag_name__, self.__a_sync_flag_value__ ) cache.is_cached = True @@ -134,7 +133,7 @@ class ASyncABC(metaclass=ASyncMeta): ) if not cache.is_cached: - cache.value = _flags.cnegate_if_necessary( + cache.value = negate_if_necessary( self.__a_sync_flag_name__, self.__a_sync_flag_value__ ) cache.is_cached = True @@ -165,7 +164,7 @@ class ASyncABC(metaclass=ASyncMeta): cdef object flag cdef bint sync if flag := _kwargs.get_flag_name_c(kwargs): - sync = _kwargs.is_sync_c(flag, kwargs, pop_flag=False) # type: ignore [arg-type] + sync = _kwargs.is_sync(flag, kwargs, pop_flag=False) # type: ignore [arg-type] logger.debug( "kwargs indicate the new instance created with args %s %s is %ssynchronous", args, diff --git a/a_sync/a_sync/base.pyi b/a_sync/a_sync/base.pyi index a3e69c17..2d3683c0 100644 --- a/a_sync/a_sync/base.pyi +++ b/a_sync/a_sync/base.pyi @@ -2,11 +2,8 @@ from a_sync._typing import * import functools from _typeshed import Incomplete from a_sync import exceptions as exceptions -from a_sync.a_sync._flags import ( - VIABLE_FLAGS as VIABLE_FLAGS, - negate_if_necessary as negate_if_necessary, -) from a_sync.a_sync.abstract import ASyncABC as ASyncABC +from a_sync.a_sync.flags import VIABLE_FLAGS as VIABLE_FLAGS logger: Incomplete diff --git a/a_sync/a_sync/base.pyx b/a_sync/a_sync/base.pyx index 5b734f5d..a50ab12a 100644 --- a/a_sync/a_sync/base.pyx +++ b/a_sync/a_sync/base.pyx @@ -5,9 +5,9 @@ from contextlib import suppress from a_sync import exceptions from a_sync._typing import * -from a_sync.a_sync._flags import VIABLE_FLAGS -from a_sync.a_sync._flags cimport cnegate_if_necessary +from a_sync.a_sync._flags cimport negate_if_necessary from a_sync.a_sync.abstract import ASyncABC +from a_sync.a_sync.flags import VIABLE_FLAGS logger = logging.getLogger(__name__) @@ -118,7 +118,7 @@ class ASyncGenericBase(ASyncABC): except exceptions.NoFlagsFound: flag = _get_a_sync_flag_name_from_class_def(cls) flag_value = _get_a_sync_flag_value_from_class_def(cls, flag) - return cnegate_if_necessary(flag, flag_value) # type: ignore [arg-type] + return negate_if_necessary(flag, flag_value) # type: ignore [arg-type] # we need an extra var so we can log it cdef bint sync @@ -130,7 +130,7 @@ class ASyncGenericBase(ASyncABC): flag = _get_a_sync_flag_name_from_class_def(cls) flag_value = _get_a_sync_flag_value_from_class_def(cls, flag) - sync = cnegate_if_necessary(flag, flag_value) # type: ignore [arg-type] + sync = negate_if_necessary(flag, flag_value) # type: ignore [arg-type] logger._log( logging.DEBUG, "`%s.%s` indicates default mode is %ssynchronous", @@ -192,7 +192,7 @@ cdef str _get_a_sync_flag_name_from_signature(object cls): return _parse_flag_name_from_list(cls, parameters) -cdef str _parse_flag_name_from_list(object cls, dict items): +cdef str _parse_flag_name_from_list(object cls, object items): cdef list[str] present_flags cdef str flag present_flags = [flag for flag in VIABLE_FLAGS if flag in items] diff --git a/a_sync/a_sync/decorator.py b/a_sync/a_sync/decorator.py index ccbf99ac..b5b9d555 100644 --- a/a_sync/a_sync/decorator.py +++ b/a_sync/a_sync/decorator.py @@ -1,7 +1,7 @@ # mypy: disable-error-code=valid-type # mypy: disable-error-code=misc from a_sync._typing import * -from a_sync.a_sync import _flags, config +from a_sync.a_sync import config from a_sync.a_sync.function import ( ASyncDecorator, ASyncFunction, diff --git a/a_sync/a_sync/flags.py b/a_sync/a_sync/flags.py new file mode 100644 index 00000000..a740ba47 --- /dev/null +++ b/a_sync/a_sync/flags.py @@ -0,0 +1,75 @@ +""" +This module provides functionality for handling synchronous and asynchronous flags +in the ez-a-sync library. + +ez-a-sync uses 'flags' to indicate whether objects or function calls will be synchronous or asynchronous. + +You can use any of the provided flags, whichever makes the most sense for your use case. + +:obj:`AFFIRMATIVE_FLAGS`: Set of flags indicating synchronous behavior. Currently includes "sync". + +:obj:`NEGATIVE_FLAGS`: Set of flags indicating asynchronous behavior. Currently includes "asynchronous". + +:obj:`VIABLE_FLAGS`: Set of all valid flags, combining both synchronous and asynchronous indicators. +""" + +from typing import Set + + +AFFIRMATIVE_FLAGS: Set[str] = {"sync"} +"""Set of flags indicating synchronous behavior. + +This set currently contains only the flag "sync", which is used to denote +synchronous operations within the ez-a-sync library. + +Examples: + >>> 'sync' in AFFIRMATIVE_FLAGS + True + + >>> 'async' in AFFIRMATIVE_FLAGS + False + +See Also: + :data:`NEGATIVE_FLAGS`: Flags indicating asynchronous behavior. + :data:`VIABLE_FLAGS`: All valid flags, combining both sync and async indicators. +""" + +NEGATIVE_FLAGS: Set[str] = {"asynchronous"} +"""Set of flags indicating asynchronous behavior. + +This set currently contains only the flag "asynchronous", which is used to denote +asynchronous operations within the ez-a-sync library. + +Examples: + >>> 'asynchronous' in NEGATIVE_FLAGS + True + + >>> 'sync' in NEGATIVE_FLAGS + False + +See Also: + :data:`AFFIRMATIVE_FLAGS`: Flags indicating synchronous behavior. + :data:`VIABLE_FLAGS`: All valid flags, combining both sync and async indicators. +""" + +VIABLE_FLAGS: Set[str] = AFFIRMATIVE_FLAGS | NEGATIVE_FLAGS +"""Set of all valid flags, combining both synchronous and asynchronous indicators. + +The ez-a-sync library uses these flags to indicate whether objects or function +calls will be synchronous or asynchronous. You can use any of the provided flags, +whichever makes the most sense for your use case. + +Examples: + >>> 'sync' in VIABLE_FLAGS + True + + >>> 'asynchronous' in VIABLE_FLAGS + True + + >>> 'invalid' in VIABLE_FLAGS + False + +See Also: + :data:`AFFIRMATIVE_FLAGS`: Flags indicating synchronous behavior. + :data:`NEGATIVE_FLAGS`: Flags indicating asynchronous behavior. +""" diff --git a/a_sync/a_sync/function.pyx b/a_sync/a_sync/function.pyx index acdf35dd..75304c54 100644 --- a/a_sync/a_sync/function.pyx +++ b/a_sync/a_sync/function.pyx @@ -10,7 +10,7 @@ from async_property.cached import AsyncCachedPropertyDescriptor # type: ignore from a_sync._typing import * from a_sync.a_sync import _helpers from a_sync.a_sync cimport _kwargs -from a_sync.a_sync._flags import VIABLE_FLAGS +from a_sync.a_sync.flags import VIABLE_FLAGS from a_sync.a_sync.modifiers.manager import ModifierManager if TYPE_CHECKING: @@ -141,12 +141,11 @@ cdef bint _run_sync(object function, dict kwargs): See Also: - :func:`_kwargs.get_flag_name` - - :func:`_kwargs.is_sync` """ cdef object flag if flag := _kwargs.get_flag_name_c(kwargs): # If a flag was specified in the kwargs, we will defer to it. - return _kwargs.is_sync_c(flag, kwargs, pop_flag=True) + return _kwargs.is_sync(flag, kwargs, pop_flag=True) else: # No flag specified in the kwargs, we will defer to 'default'. return function._sync_default diff --git a/a_sync/a_sync/method.pyx b/a_sync/a_sync/method.pyx index 2d48fa06..3c16caac 100644 --- a/a_sync/a_sync/method.pyx +++ b/a_sync/a_sync/method.pyx @@ -773,7 +773,7 @@ class ASyncBoundMethod(ASyncFunction[P, T], Generic[I, P, T]): """ cdef object flag if flag := _kwargs.get_flag_name_c(kwargs): - return _kwargs.is_sync_c(flag, kwargs, pop_flag=True) # type: ignore [arg-type] + return _kwargs.is_sync(flag, kwargs, pop_flag=True) # type: ignore [arg-type] elif self.default: return self.default == "sync" elif _is_a_sync_instance(self.__self__): diff --git a/a_sync/exceptions.py b/a_sync/exceptions.py index 531b6267..f1261506 100644 --- a/a_sync/exceptions.py +++ b/a_sync/exceptions.py @@ -5,7 +5,7 @@ import asyncio from a_sync._typing import * -from a_sync.a_sync._flags import VIABLE_FLAGS +from a_sync.a_sync.flags import VIABLE_FLAGS if TYPE_CHECKING: from a_sync import TaskMapping