dycw-utilities 0.123.0__tar.gz → 0.124.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/PKG-INFO +1 -1
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/pyproject.toml +2 -2
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_asyncio.py +119 -61
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_redis.py +2 -2
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_sqlalchemy.py +1 -1
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/__init__.py +1 -1
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/asyncio.py +179 -41
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/redis.py +2 -2
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/slack_sdk.py +6 -10
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/sqlalchemy.py +2 -1
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/.gitignore +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/LICENSE +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/README.md +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/__init__.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/conftest.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/modules/__init__.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/modules/package_missing/__init__.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/modules/package_missing/module.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/modules/package_with/__init__.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/modules/package_with/outer_1.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/modules/package_with/outer_2.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/modules/package_without/__init__.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/modules/package_without/module_1.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/modules/package_without/module_2.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/modules/standalone.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/modules/with_imports.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_altair.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_astor.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_atomicwrites.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_atools.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_cachetools.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_click.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_concurrent.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_contextlib.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_contextvars.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_cryptography.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_cvxpy.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_dataclasses.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_datetime.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_enum.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_errors.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_eventkit.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_fastapi.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_fpdf2.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_functions.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_functools.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_getpass.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_git.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_hashlib.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_http.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_hypothesis.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_importlib.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_ipython.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_iterables.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_jupyter.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_lightweight_charts.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_logging.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_loguru.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_luigi.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_math.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_memory_profiler.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_modules.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_more_itertools.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_numpy.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_operator.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_optuna.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_orjson.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_os.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_parse.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_pathlib.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_period.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_pickle.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_platform.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_polars.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_polars_ols.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_pqdm.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_pydantic.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_pyinstrument.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_pyrsistent.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_pytest.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_pytest_regressions.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_python_dotenv.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_random.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_re.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_reprlib.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_rich.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_scipy.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_sentinel.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_shelve.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_slack_sdk.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_socket.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_sqlalchemy_polars.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_statsmodel.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_streamlit.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_sys.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_tempfile.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_tenacity.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_text.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_threading.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_timer.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_traceback.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_traceback_funcs/__init__.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_traceback_funcs/chain.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_traceback_funcs/error_bind.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_traceback_funcs/many.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_traceback_funcs/one.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_traceback_funcs/recursive.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_traceback_funcs/two.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_traceback_funcs/untraced.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_types.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_typing.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_typing_funcs/__init__.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_typing_funcs/no_future.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_typing_funcs/with_future.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_tzdata.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_tzlocal.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_uuid.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_version.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_warnings.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_whenever.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_zipfile.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/tests/test_zoneinfo.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/altair.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/astor.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/atomicwrites.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/atools.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/cachetools.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/click.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/concurrent.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/contextlib.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/contextvars.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/cryptography.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/cvxpy.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/dataclasses.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/datetime.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/enum.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/errors.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/eventkit.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/fastapi.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/fpdf2.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/functions.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/functools.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/getpass.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/git.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/hashlib.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/http.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/hypothesis.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/importlib.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/ipython.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/iterables.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/jupyter.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/lightweight_charts.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/logging.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/loguru.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/luigi.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/math.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/memory_profiler.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/modules.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/more_itertools.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/numpy.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/operator.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/optuna.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/orjson.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/os.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/parse.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/pathlib.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/period.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/pickle.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/platform.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/polars.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/polars_ols.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/pqdm.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/py.typed +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/pydantic.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/pyinstrument.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/pyrsistent.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/pytest.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/pytest_regressions.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/python_dotenv.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/random.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/re.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/reprlib.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/rich.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/scipy.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/sentinel.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/shelve.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/socket.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/sqlalchemy_polars.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/statsmodels.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/streamlit.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/sys.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/tempfile.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/tenacity.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/text.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/threading.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/timer.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/traceback.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/types.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/typing.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/tzdata.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/tzlocal.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/uuid.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/version.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/warnings.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/whenever.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/zipfile.py +0 -0
- {dycw_utilities-0.123.0 → dycw_utilities-0.124.0}/src/utilities/zoneinfo.py +0 -0
@@ -92,7 +92,7 @@ dependencies = [
|
|
92
92
|
name = "dycw-utilities"
|
93
93
|
readme = "README.md"
|
94
94
|
requires-python = ">= 3.12"
|
95
|
-
version = "0.
|
95
|
+
version = "0.124.0"
|
96
96
|
|
97
97
|
[project.optional-dependencies]
|
98
98
|
test = [
|
@@ -334,7 +334,7 @@ zzz-test-zoneinfo = [
|
|
334
334
|
# bump-my-version
|
335
335
|
[tool.bumpversion]
|
336
336
|
allow_dirty = true
|
337
|
-
current_version = "0.
|
337
|
+
current_version = "0.124.0"
|
338
338
|
|
339
339
|
[[tool.bumpversion.files]]
|
340
340
|
filename = "src/utilities/__init__.py"
|
@@ -1,6 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from asyncio import CancelledError, Event, Queue,
|
3
|
+
from asyncio import CancelledError, Event, Queue, run, sleep
|
4
|
+
from collections import deque
|
4
5
|
from contextlib import asynccontextmanager
|
5
6
|
from dataclasses import dataclass, field
|
6
7
|
from functools import partial
|
@@ -8,9 +9,10 @@ from itertools import chain, count
|
|
8
9
|
from re import search
|
9
10
|
from typing import TYPE_CHECKING, Any, ClassVar, Self, cast, override
|
10
11
|
|
11
|
-
from hypothesis import HealthCheck, Phase, given, settings
|
12
|
+
from hypothesis import HealthCheck, Phase, assume, given, settings
|
12
13
|
from hypothesis.strategies import (
|
13
14
|
DataObject,
|
15
|
+
booleans,
|
14
16
|
data,
|
15
17
|
integers,
|
16
18
|
just,
|
@@ -22,6 +24,7 @@ from hypothesis.strategies import (
|
|
22
24
|
from pytest import LogCaptureFixture, mark, param, raises
|
23
25
|
|
24
26
|
from utilities.asyncio import (
|
27
|
+
EnhancedQueue,
|
25
28
|
EnhancedTaskGroup,
|
26
29
|
InfiniteLooper,
|
27
30
|
InfiniteQueueLooper,
|
@@ -65,6 +68,77 @@ if TYPE_CHECKING:
|
|
65
68
|
)
|
66
69
|
|
67
70
|
|
71
|
+
class TestEnhancedQueue:
|
72
|
+
@given(
|
73
|
+
xs=lists(integers()),
|
74
|
+
wait=booleans(),
|
75
|
+
put_all=booleans(),
|
76
|
+
get_reverse=booleans(),
|
77
|
+
)
|
78
|
+
async def test_left(
|
79
|
+
self, *, xs: list[int], wait: int, put_all: bool, get_reverse: bool
|
80
|
+
) -> None:
|
81
|
+
_ = assume(not ((len(xs) == 0) and wait))
|
82
|
+
deq: deque[int] = deque()
|
83
|
+
for x in xs:
|
84
|
+
deq.appendleft(x)
|
85
|
+
queue: EnhancedQueue[int] = EnhancedQueue()
|
86
|
+
if put_all:
|
87
|
+
if wait:
|
88
|
+
await queue.put_left(*xs)
|
89
|
+
else:
|
90
|
+
queue.put_left_nowait(*xs)
|
91
|
+
else:
|
92
|
+
for i, x in enumerate(xs, start=1):
|
93
|
+
if wait:
|
94
|
+
await queue.put_left(x)
|
95
|
+
else:
|
96
|
+
queue.put_left_nowait(x)
|
97
|
+
assert queue.qsize() == i
|
98
|
+
assert list(deq) == xs[::-1]
|
99
|
+
if wait:
|
100
|
+
res = await queue.get_all(reverse=get_reverse)
|
101
|
+
else:
|
102
|
+
res = queue.get_all_nowait(reverse=get_reverse)
|
103
|
+
expected = xs if get_reverse else xs[::-1]
|
104
|
+
assert res == expected
|
105
|
+
|
106
|
+
@given(
|
107
|
+
xs=lists(integers()),
|
108
|
+
wait=booleans(),
|
109
|
+
put_all=booleans(),
|
110
|
+
get_reverse=booleans(),
|
111
|
+
)
|
112
|
+
async def test_right(
|
113
|
+
self, *, xs: list[int], wait: int, put_all: bool, get_reverse: bool
|
114
|
+
) -> None:
|
115
|
+
_ = assume(not ((len(xs) == 0) and wait))
|
116
|
+
deq: deque[int] = deque()
|
117
|
+
for x in xs:
|
118
|
+
deq.append(x)
|
119
|
+
queue: EnhancedQueue[int] = EnhancedQueue()
|
120
|
+
if put_all:
|
121
|
+
if wait:
|
122
|
+
await queue.put_right(*xs)
|
123
|
+
else:
|
124
|
+
queue.put_right_nowait(*xs)
|
125
|
+
assert queue.qsize() == len(xs)
|
126
|
+
else:
|
127
|
+
for i, x in enumerate(xs, start=1):
|
128
|
+
if wait:
|
129
|
+
await queue.put_right(x)
|
130
|
+
else:
|
131
|
+
queue.put_right_nowait(x)
|
132
|
+
assert queue.qsize() == i
|
133
|
+
assert list(deq) == xs
|
134
|
+
if wait:
|
135
|
+
res = await queue.get_all(reverse=get_reverse)
|
136
|
+
else:
|
137
|
+
res = queue.get_all_nowait(reverse=get_reverse)
|
138
|
+
expected = xs[::-1] if get_reverse else xs
|
139
|
+
assert res == expected
|
140
|
+
|
141
|
+
|
68
142
|
class TestEnhancedTaskGroup:
|
69
143
|
async def test_create_task_context_coroutine(self) -> None:
|
70
144
|
flag: bool = False
|
@@ -175,6 +249,24 @@ class TestGetEvent:
|
|
175
249
|
assert get_event(event=lambda: event) is event
|
176
250
|
|
177
251
|
|
252
|
+
class TestGetItems:
|
253
|
+
@given(
|
254
|
+
xs=lists(integers(), min_size=1),
|
255
|
+
max_size=integers(1, 10) | none(),
|
256
|
+
wait=booleans(),
|
257
|
+
)
|
258
|
+
async def test_main(
|
259
|
+
self, *, xs: list[int], max_size: int | None, wait: bool
|
260
|
+
) -> None:
|
261
|
+
queue: Queue[int] = Queue()
|
262
|
+
put_items_nowait(xs, queue)
|
263
|
+
if wait:
|
264
|
+
result = await get_items(queue, max_size=max_size)
|
265
|
+
else:
|
266
|
+
result = get_items_nowait(queue, max_size=max_size)
|
267
|
+
assert result == xs[:max_size]
|
268
|
+
|
269
|
+
|
178
270
|
class TestInfiniteLooper:
|
179
271
|
sleep_restart_cases: ClassVar[list[Any]] = [
|
180
272
|
param(60.0, "for 0:01:00"),
|
@@ -386,9 +478,9 @@ class TestInfiniteLooper:
|
|
386
478
|
Example(sleep_core=0.05, sleep_restart=0.05) as looper,
|
387
479
|
):
|
388
480
|
...
|
389
|
-
assert
|
390
|
-
assert 0 <= looper.counter <=
|
391
|
-
assert
|
481
|
+
assert 3 <= looper.initializations <= 7
|
482
|
+
assert 0 <= looper.counter <= 8
|
483
|
+
assert 13 <= external <= 22
|
392
484
|
|
393
485
|
async def test_with_coroutine_self_error(self) -> None:
|
394
486
|
class CustomError(Exception): ...
|
@@ -661,13 +753,13 @@ class TestInfiniteQueueLooper:
|
|
661
753
|
counter: int = 0
|
662
754
|
|
663
755
|
@override
|
664
|
-
async def
|
665
|
-
self.counter += len(
|
756
|
+
async def _process_queue(self) -> None:
|
757
|
+
self.counter += len(self._queue.get_all_nowait())
|
666
758
|
|
667
759
|
async with timeout_dur(duration=1.0), Example(sleep_core=0.05) as looper:
|
668
760
|
await sleep(0.1)
|
669
761
|
for i in range(10):
|
670
|
-
looper.
|
762
|
+
looper.put_right_nowait(i)
|
671
763
|
await sleep(0.05)
|
672
764
|
|
673
765
|
assert looper.counter == 10
|
@@ -678,13 +770,13 @@ class TestInfiniteQueueLooper:
|
|
678
770
|
output: set[int] = field(default_factory=set)
|
679
771
|
|
680
772
|
@override
|
681
|
-
async def
|
682
|
-
self.output.update(
|
773
|
+
async def _process_queue(self) -> None:
|
774
|
+
self.output.update(self._queue.get_all_nowait())
|
683
775
|
|
684
776
|
looper = Example(sleep_core=0.05)
|
685
777
|
assert len(looper) == 0
|
686
778
|
assert looper.empty()
|
687
|
-
looper.
|
779
|
+
looper.put_right_nowait(*range(n))
|
688
780
|
assert len(looper) == n
|
689
781
|
assert not looper.empty()
|
690
782
|
|
@@ -694,11 +786,11 @@ class TestInfiniteQueueLooper:
|
|
694
786
|
output: set[int] = field(default_factory=set)
|
695
787
|
|
696
788
|
@override
|
697
|
-
async def
|
698
|
-
self.output.update(
|
789
|
+
async def _process_queue(self) -> None:
|
790
|
+
self.output.update(self._queue.get_all_nowait())
|
699
791
|
|
700
792
|
looper = Example(sleep_core=0.05)
|
701
|
-
looper.
|
793
|
+
looper.put_right_nowait(*range(10))
|
702
794
|
async with looper:
|
703
795
|
await looper.run_until_empty()
|
704
796
|
assert looper.empty()
|
@@ -715,66 +807,32 @@ class TestInfiniteQueueLooper:
|
|
715
807
|
output: set[int] = field(default_factory=set)
|
716
808
|
|
717
809
|
@override
|
718
|
-
async def
|
719
|
-
raise CustomError
|
810
|
+
async def _process_queue(self) -> None:
|
811
|
+
raise CustomError
|
720
812
|
|
721
813
|
async with (
|
722
814
|
timeout_dur(duration=1.0),
|
723
815
|
Example(sleep_core=0.05, logger=logger) as looper,
|
724
816
|
):
|
725
|
-
looper.
|
817
|
+
looper.put_left_nowait(1)
|
726
818
|
if logger is not None:
|
727
819
|
message = caplog.messages[0]
|
728
|
-
expected = "'Example' encountered CustomError(
|
820
|
+
expected = "'Example' encountered 'CustomError()'; sleeping for 0:01:00..."
|
729
821
|
assert message == expected
|
730
822
|
|
731
823
|
|
732
|
-
class
|
733
|
-
@given(xs=lists(integers(), min_size=1),
|
734
|
-
async def
|
824
|
+
class TestPutItems:
|
825
|
+
@given(xs=lists(integers(), min_size=1), wait=booleans())
|
826
|
+
async def test_main(self, *, xs: list[int], wait: bool) -> None:
|
735
827
|
queue: Queue[int] = Queue()
|
736
|
-
|
737
|
-
|
738
|
-
if max_size is None:
|
739
|
-
assert result == xs
|
828
|
+
if wait:
|
829
|
+
put_items_nowait(xs, queue)
|
740
830
|
else:
|
741
|
-
assert result == xs[:max_size]
|
742
|
-
|
743
|
-
@given(xs=lists(integers(), min_size=1), max_size=integers(1, 10) | none())
|
744
|
-
async def test_get_then_put(self, *, xs: list[int], max_size: int | None) -> None:
|
745
|
-
queue: Queue[int] = Queue()
|
746
|
-
|
747
|
-
async def put() -> None:
|
748
|
-
await sleep(0.01)
|
749
831
|
await put_items(xs, queue)
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
result = task.result()
|
755
|
-
if max_size is None:
|
756
|
-
assert result == xs
|
757
|
-
else:
|
758
|
-
assert result == xs[:max_size]
|
759
|
-
|
760
|
-
async def test_empty(self) -> None:
|
761
|
-
queue: Queue[int] = Queue()
|
762
|
-
with raises(TimeoutError): # noqa: PT012
|
763
|
-
async with timeout(0.01), TaskGroup() as tg:
|
764
|
-
_ = tg.create_task(get_items(queue))
|
765
|
-
_ = tg.create_task(sleep(0.02))
|
766
|
-
|
767
|
-
|
768
|
-
class TestPutAndGetItemsNoWait:
|
769
|
-
@given(xs=lists(integers(), min_size=1), max_size=integers(1, 10) | none())
|
770
|
-
def test_main(self, *, xs: list[int], max_size: int | None) -> None:
|
771
|
-
queue: Queue[int] = Queue()
|
772
|
-
put_items_nowait(xs, queue)
|
773
|
-
result = get_items_nowait(queue, max_size=max_size)
|
774
|
-
if max_size is None:
|
775
|
-
assert result == xs
|
776
|
-
else:
|
777
|
-
assert result == xs[:max_size]
|
832
|
+
result: list[int] = []
|
833
|
+
while not queue.empty():
|
834
|
+
result.append(await queue.get())
|
835
|
+
assert result == xs
|
778
836
|
|
779
837
|
|
780
838
|
class TestUniquePriorityQueue:
|
@@ -150,7 +150,7 @@ class TestPublisher:
|
|
150
150
|
|
151
151
|
async def sleep_then_put() -> None:
|
152
152
|
await sleep(0.1)
|
153
|
-
publisher.
|
153
|
+
publisher.put_right_nowait((channel, obj))
|
154
154
|
|
155
155
|
with raises(ExceptionGroup): # noqa: PT012
|
156
156
|
async with EnhancedTaskGroup(timeout=1.0) as tg:
|
@@ -187,7 +187,7 @@ class TestPublisher:
|
|
187
187
|
|
188
188
|
async def sleep_then_put() -> None:
|
189
189
|
await sleep(0.1)
|
190
|
-
publisher.
|
190
|
+
publisher.put_right_nowait((channel, text))
|
191
191
|
|
192
192
|
with raises(ExceptionGroup): # noqa: PT012
|
193
193
|
async with EnhancedTaskGroup(timeout=1.0) as tg:
|
@@ -1169,7 +1169,7 @@ class TestUpserter:
|
|
1169
1169
|
engine = await sqlalchemy_engines(data, table)
|
1170
1170
|
pairs = [(id_, init) for id_, init, _ in triples]
|
1171
1171
|
async with Upserter(duration=1.0, sleep_core=0.1, engine=engine) as upserter:
|
1172
|
-
upserter.
|
1172
|
+
upserter.put_right_nowait((pairs, table))
|
1173
1173
|
|
1174
1174
|
sel = select(table)
|
1175
1175
|
async with engine.begin() as conn:
|
@@ -8,6 +8,7 @@ from asyncio import (
|
|
8
8
|
PriorityQueue,
|
9
9
|
Queue,
|
10
10
|
QueueEmpty,
|
11
|
+
QueueFull,
|
11
12
|
Semaphore,
|
12
13
|
StreamReader,
|
13
14
|
Task,
|
@@ -27,6 +28,7 @@ from contextlib import (
|
|
27
28
|
)
|
28
29
|
from dataclasses import dataclass, field
|
29
30
|
from io import StringIO
|
31
|
+
from itertools import chain
|
30
32
|
from logging import getLogger
|
31
33
|
from subprocess import PIPE
|
32
34
|
from sys import stderr, stdout
|
@@ -43,6 +45,8 @@ from typing import (
|
|
43
45
|
override,
|
44
46
|
)
|
45
47
|
|
48
|
+
from typing_extensions import deprecated
|
49
|
+
|
46
50
|
from utilities.datetime import (
|
47
51
|
MINUTE,
|
48
52
|
SECOND,
|
@@ -53,7 +57,6 @@ from utilities.datetime import (
|
|
53
57
|
)
|
54
58
|
from utilities.errors import ImpossibleCaseError, repr_error
|
55
59
|
from utilities.functions import ensure_int, ensure_not_none, get_class_name
|
56
|
-
from utilities.reprlib import get_repr
|
57
60
|
from utilities.sentinel import Sentinel, sentinel
|
58
61
|
from utilities.types import (
|
59
62
|
Coroutine1,
|
@@ -67,6 +70,7 @@ from utilities.types import (
|
|
67
70
|
if TYPE_CHECKING:
|
68
71
|
from asyncio import _CoroutineLike
|
69
72
|
from asyncio.subprocess import Process
|
73
|
+
from collections import deque
|
70
74
|
from collections.abc import AsyncIterator, Sequence
|
71
75
|
from contextvars import Context
|
72
76
|
from types import TracebackType
|
@@ -77,6 +81,164 @@ if TYPE_CHECKING:
|
|
77
81
|
_T = TypeVar("_T")
|
78
82
|
|
79
83
|
|
84
|
+
class EnhancedQueue(Queue[_T]):
|
85
|
+
"""An asynchronous deque."""
|
86
|
+
|
87
|
+
@override
|
88
|
+
def __init__(self, maxsize: int = 0) -> None:
|
89
|
+
super().__init__(maxsize=maxsize)
|
90
|
+
self._finished: Event
|
91
|
+
self._getters: deque[Any]
|
92
|
+
self._putters: deque[Any]
|
93
|
+
self._queue: deque[_T]
|
94
|
+
self._unfinished_tasks: int
|
95
|
+
|
96
|
+
@override
|
97
|
+
@deprecated("Use `get_left`/`get_right` instead")
|
98
|
+
async def get(self) -> _T:
|
99
|
+
raise RuntimeError # pragma: no cover
|
100
|
+
|
101
|
+
@override
|
102
|
+
@deprecated("Use `get_left_nowait`/`get_right_nowait` instead")
|
103
|
+
def get_nowait(self) -> _T:
|
104
|
+
raise RuntimeError # pragma: no cover
|
105
|
+
|
106
|
+
@override
|
107
|
+
@deprecated("Use `put_left`/`put_right` instead")
|
108
|
+
async def put(self, item: _T) -> None:
|
109
|
+
raise RuntimeError(item) # pragma: no cover
|
110
|
+
|
111
|
+
@override
|
112
|
+
@deprecated("Use `put_left_nowait`/`put_right_nowait` instead")
|
113
|
+
def put_nowait(self, item: _T) -> None:
|
114
|
+
raise RuntimeError(item) # pragma: no cover
|
115
|
+
|
116
|
+
# get all
|
117
|
+
|
118
|
+
async def get_all(self, *, reverse: bool = False) -> Sequence[_T]:
|
119
|
+
"""Remove and return all items from the queue."""
|
120
|
+
first = await (self.get_right() if reverse else self.get_left())
|
121
|
+
return list(chain([first], self.get_all_nowait(reverse=reverse)))
|
122
|
+
|
123
|
+
def get_all_nowait(self, *, reverse: bool = False) -> Sequence[_T]:
|
124
|
+
"""Remove and return all items from the queue without blocking."""
|
125
|
+
items: Sequence[_T] = []
|
126
|
+
while True:
|
127
|
+
try:
|
128
|
+
items.append(
|
129
|
+
self.get_right_nowait() if reverse else self.get_left_nowait()
|
130
|
+
)
|
131
|
+
except QueueEmpty:
|
132
|
+
return items
|
133
|
+
|
134
|
+
# get left/right
|
135
|
+
|
136
|
+
async def get_left(self) -> _T:
|
137
|
+
"""Remove and return an item from the start of the queue."""
|
138
|
+
return await self._get_left_or_right(self._get)
|
139
|
+
|
140
|
+
async def get_right(self) -> _T:
|
141
|
+
"""Remove and return an item from the end of the queue."""
|
142
|
+
return await self._get_left_or_right(self._get_right)
|
143
|
+
|
144
|
+
def get_left_nowait(self) -> _T:
|
145
|
+
"""Remove and return an item from the start of the queue without blocking."""
|
146
|
+
return self._get_left_or_right_nowait(self._get)
|
147
|
+
|
148
|
+
def get_right_nowait(self) -> _T:
|
149
|
+
"""Remove and return an item from the end of the queue without blocking."""
|
150
|
+
return self._get_left_or_right_nowait(self._get_right)
|
151
|
+
|
152
|
+
# put left/right
|
153
|
+
|
154
|
+
async def put_left(self, *items: _T) -> None:
|
155
|
+
"""Put items into the queue at the start."""
|
156
|
+
return await self._put_left_or_right(self._put_left, *items)
|
157
|
+
|
158
|
+
async def put_right(self, *items: _T) -> None:
|
159
|
+
"""Put items into the queue at the end."""
|
160
|
+
return await self._put_left_or_right(self._put, *items)
|
161
|
+
|
162
|
+
def put_left_nowait(self, *items: _T) -> None:
|
163
|
+
"""Put items into the queue at the start without blocking."""
|
164
|
+
self._put_left_or_right_nowait(self._put_left, *items)
|
165
|
+
|
166
|
+
def put_right_nowait(self, *items: _T) -> None:
|
167
|
+
"""Put items into the queue at the end without blocking."""
|
168
|
+
self._put_left_or_right_nowait(self._put, *items)
|
169
|
+
|
170
|
+
# private
|
171
|
+
|
172
|
+
def _put_left(self, item: _T) -> None:
|
173
|
+
self._queue.appendleft(item)
|
174
|
+
|
175
|
+
def _get_right(self) -> _T:
|
176
|
+
return self._queue.pop()
|
177
|
+
|
178
|
+
async def _get_left_or_right(self, getter_use: Callable[[], _T], /) -> _T:
|
179
|
+
while self.empty(): # pragma: no cover
|
180
|
+
getter = self._get_loop().create_future() # pyright: ignore[reportAttributeAccessIssue]
|
181
|
+
self._getters.append(getter)
|
182
|
+
try:
|
183
|
+
await getter
|
184
|
+
except:
|
185
|
+
getter.cancel()
|
186
|
+
with suppress(ValueError):
|
187
|
+
self._getters.remove(getter)
|
188
|
+
if not self.empty() and not getter.cancelled():
|
189
|
+
self._wakeup_next(self._getters) # pyright: ignore[reportAttributeAccessIssue]
|
190
|
+
raise
|
191
|
+
return getter_use()
|
192
|
+
|
193
|
+
def _get_left_or_right_nowait(self, getter: Callable[[], _T], /) -> _T:
|
194
|
+
if self.empty():
|
195
|
+
raise QueueEmpty
|
196
|
+
item = getter()
|
197
|
+
self._wakeup_next(self._putters) # pyright: ignore[reportAttributeAccessIssue]
|
198
|
+
return item
|
199
|
+
|
200
|
+
async def _put_left_or_right(
|
201
|
+
self, putter_use: Callable[[_T], None], /, *items: _T
|
202
|
+
) -> None:
|
203
|
+
"""Put an item into the queue."""
|
204
|
+
for item in items:
|
205
|
+
await self._put_left_or_right_one(putter_use, item)
|
206
|
+
|
207
|
+
async def _put_left_or_right_one(
|
208
|
+
self, putter_use: Callable[[_T], None], item: _T, /
|
209
|
+
) -> None:
|
210
|
+
"""Put an item into the queue."""
|
211
|
+
while self.full(): # pragma: no cover
|
212
|
+
putter = self._get_loop().create_future() # pyright: ignore[reportAttributeAccessIssue]
|
213
|
+
self._putters.append(putter)
|
214
|
+
try:
|
215
|
+
await putter
|
216
|
+
except:
|
217
|
+
putter.cancel()
|
218
|
+
with suppress(ValueError):
|
219
|
+
self._putters.remove(putter)
|
220
|
+
if not self.full() and not putter.cancelled():
|
221
|
+
self._wakeup_next(self._putters) # pyright: ignore[reportAttributeAccessIssue]
|
222
|
+
raise
|
223
|
+
return putter_use(item)
|
224
|
+
|
225
|
+
def _put_left_or_right_nowait(
|
226
|
+
self, putter: Callable[[_T], None], /, *items: _T
|
227
|
+
) -> None:
|
228
|
+
for item in items:
|
229
|
+
self._put_left_or_right_nowait_one(putter, item)
|
230
|
+
|
231
|
+
def _put_left_or_right_nowait_one(
|
232
|
+
self, putter: Callable[[_T], None], item: _T, /
|
233
|
+
) -> None:
|
234
|
+
if self.full(): # pragma: no cover
|
235
|
+
raise QueueFull
|
236
|
+
putter(item)
|
237
|
+
self._unfinished_tasks += 1
|
238
|
+
self._finished.clear()
|
239
|
+
self._wakeup_next(self._getters) # pyright: ignore[reportAttributeAccessIssue]
|
240
|
+
|
241
|
+
|
80
242
|
##
|
81
243
|
|
82
244
|
|
@@ -428,14 +590,13 @@ class _InfiniteLooperDefaultEventError(InfiniteLooperError):
|
|
428
590
|
class InfiniteQueueLooper(InfiniteLooper[THashable], Generic[THashable, _T]):
|
429
591
|
"""An infinite loop which processes a queue."""
|
430
592
|
|
431
|
-
queue_type: type[Queue[_T]] = field(default=Queue, repr=False)
|
432
593
|
_await_upon_aenter: bool = field(default=False, init=False, repr=False)
|
433
|
-
_queue:
|
594
|
+
_queue: EnhancedQueue[_T] = field(init=False, repr=False)
|
434
595
|
|
435
596
|
@override
|
436
597
|
def __post_init__(self) -> None:
|
437
598
|
super().__post_init__()
|
438
|
-
self._queue =
|
599
|
+
self._queue = EnhancedQueue()
|
439
600
|
|
440
601
|
def __len__(self) -> int:
|
441
602
|
return self._queue.qsize()
|
@@ -443,55 +604,32 @@ class InfiniteQueueLooper(InfiniteLooper[THashable], Generic[THashable, _T]):
|
|
443
604
|
@override
|
444
605
|
async def _core(self) -> None:
|
445
606
|
"""Run the core part of the loop."""
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
except Exception as error: # noqa: BLE001
|
450
|
-
raise InfiniteQueueLooperError(
|
451
|
-
looper=self, items=items, error=error
|
452
|
-
) from None
|
607
|
+
first = await self._queue.get_left()
|
608
|
+
self._queue.put_left_nowait(first)
|
609
|
+
await self._process_queue()
|
453
610
|
|
454
611
|
@abstractmethod
|
455
|
-
async def
|
456
|
-
"""Process the
|
612
|
+
async def _process_queue(self) -> None:
|
613
|
+
"""Process the queue."""
|
457
614
|
|
458
615
|
def empty(self) -> bool:
|
459
616
|
"""Check if the queue is empty."""
|
460
617
|
return self._queue.empty()
|
461
618
|
|
462
|
-
def
|
463
|
-
"""Put items into the queue."""
|
464
|
-
|
619
|
+
def put_left_nowait(self, *items: _T) -> None:
|
620
|
+
"""Put items into the queue at the start without blocking."""
|
621
|
+
self._queue.put_left_nowait(*items) # pragma: no cover
|
622
|
+
|
623
|
+
def put_right_nowait(self, *items: _T) -> None:
|
624
|
+
"""Put items into the queue at the end without blocking."""
|
625
|
+
self._queue.put_right_nowait(*items) # pragma: no cover
|
465
626
|
|
466
627
|
async def run_until_empty(self) -> None:
|
467
628
|
"""Run until the queue is empty."""
|
468
629
|
while not self.empty():
|
469
|
-
await self.
|
630
|
+
await self._process_queue()
|
470
631
|
await self.stop()
|
471
632
|
|
472
|
-
@override
|
473
|
-
def _error_upon_core(self, error: Exception, /) -> None:
|
474
|
-
"""Handle any errors upon running the core function."""
|
475
|
-
if self.logger is not None:
|
476
|
-
if isinstance(error, InfiniteQueueLooperError):
|
477
|
-
getLogger(name=self.logger).error(
|
478
|
-
"%r encountered %s whilst processing %d item(s) %s; sleeping %s...",
|
479
|
-
get_class_name(self),
|
480
|
-
repr_error(error.error),
|
481
|
-
len(error.items),
|
482
|
-
get_repr(error.items),
|
483
|
-
self._sleep_restart_desc,
|
484
|
-
)
|
485
|
-
else:
|
486
|
-
super()._error_upon_core(error) # pragma: no cover
|
487
|
-
|
488
|
-
|
489
|
-
@dataclass(kw_only=True, slots=True)
|
490
|
-
class InfiniteQueueLooperError(Exception, Generic[_T]):
|
491
|
-
looper: InfiniteQueueLooper[Any, Any]
|
492
|
-
items: Sequence[_T]
|
493
|
-
error: Exception
|
494
|
-
|
495
633
|
|
496
634
|
##
|
497
635
|
|
@@ -715,11 +853,11 @@ async def timeout_dur(
|
|
715
853
|
|
716
854
|
|
717
855
|
__all__ = [
|
856
|
+
"EnhancedQueue",
|
718
857
|
"EnhancedTaskGroup",
|
719
858
|
"InfiniteLooper",
|
720
859
|
"InfiniteLooperError",
|
721
860
|
"InfiniteQueueLooper",
|
722
|
-
"InfiniteQueueLooperError",
|
723
861
|
"StreamCommandOutput",
|
724
862
|
"UniquePriorityQueue",
|
725
863
|
"UniqueQueue",
|
@@ -597,8 +597,8 @@ class Publisher(InfiniteQueueLooper[None, tuple[str, EncodableT]]):
|
|
597
597
|
timeout: Duration = _PUBLISH_TIMEOUT
|
598
598
|
|
599
599
|
@override
|
600
|
-
async def
|
601
|
-
for item in
|
600
|
+
async def _process_queue(self) -> None:
|
601
|
+
for item in self._queue.get_all_nowait(): # skipif-ci-and-not-linux
|
602
602
|
channel, data = item
|
603
603
|
_ = await publish(
|
604
604
|
self.redis,
|
@@ -1,6 +1,5 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from asyncio import Queue
|
4
3
|
from dataclasses import dataclass
|
5
4
|
from http import HTTPStatus
|
6
5
|
from logging import NOTSET, Handler, LogRecord
|
@@ -47,15 +46,12 @@ class SlackHandler(Handler, InfiniteQueueLooper[None, str]):
|
|
47
46
|
level: int = NOTSET,
|
48
47
|
sleep_core: Duration = _SLEEP,
|
49
48
|
sleep_restart: Duration = _SLEEP,
|
50
|
-
queue_type: type[Queue[str]] = Queue,
|
51
49
|
sender: Callable[[str, str], Coroutine1[None]] = _send_adapter,
|
52
50
|
timeout: Duration = _TIMEOUT,
|
53
51
|
) -> None:
|
54
|
-
InfiniteQueueLooper.__init__( # InfiniteQueueLooper first
|
55
|
-
self, queue_type=queue_type
|
56
|
-
)
|
52
|
+
InfiniteQueueLooper.__init__(self) # InfiniteQueueLooper first
|
57
53
|
InfiniteQueueLooper.__post_init__(self)
|
58
|
-
Handler.__init__(self, level=level)
|
54
|
+
Handler.__init__(self, level=level) # Handler next
|
59
55
|
self.url = url
|
60
56
|
self.sender = sender
|
61
57
|
self.timeout = timeout
|
@@ -65,14 +61,14 @@ class SlackHandler(Handler, InfiniteQueueLooper[None, str]):
|
|
65
61
|
@override
|
66
62
|
def emit(self, record: LogRecord) -> None:
|
67
63
|
try:
|
68
|
-
self.
|
64
|
+
self.put_right_nowait(self.format(record))
|
69
65
|
except Exception: # noqa: BLE001 # pragma: no cover
|
70
66
|
self.handleError(record)
|
71
67
|
|
72
68
|
@override
|
73
|
-
async def
|
74
|
-
|
75
|
-
text = "\n".join(
|
69
|
+
async def _process_queue(self) -> None:
|
70
|
+
messages = self._queue.get_all_nowait()
|
71
|
+
text = "\n".join(messages)
|
76
72
|
async with timeout_dur(duration=self.timeout):
|
77
73
|
await self.sender(self.url, text)
|
78
74
|
|