dycw-utilities 0.115.0__tar.gz → 0.116.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.115.0 → dycw_utilities-0.116.0}/PKG-INFO +1 -1
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/pyproject.toml +2 -2
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_asyncio.py +128 -48
- dycw_utilities-0.116.0/src/tests/test_errors.py +28 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_slack_sdk.py +6 -4
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/__init__.py +1 -1
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/asyncio.py +66 -21
- dycw_utilities-0.116.0/src/utilities/errors.py +34 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/redis.py +3 -3
- dycw_utilities-0.115.0/src/tests/test_errors.py +0 -12
- dycw_utilities-0.115.0/src/utilities/errors.py +0 -17
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/.gitignore +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/LICENSE +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/README.md +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/__init__.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/conftest.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/modules/__init__.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/modules/package_missing/__init__.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/modules/package_missing/module.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/modules/package_with/__init__.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/modules/package_with/outer_1.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/modules/package_with/outer_2.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/modules/package_without/__init__.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/modules/package_without/module_1.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/modules/package_without/module_2.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/modules/standalone.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/modules/with_imports.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/scripts/__init__.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/scripts/test_async_service/__init__.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/scripts/test_async_service/__main__.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/scripts/test_async_service/run.sh +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/scripts/test_queue_processor/__init__.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/scripts/test_queue_processor/__main__.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/scripts/test_queue_processor/run.sh +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_altair.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_astor.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_atomicwrites.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_atools.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_cachetools.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_click.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_concurrent.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_contextlib.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_contextvars.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_cryptography.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_cvxpy.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_dataclasses.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_datetime.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_enum.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_eventkit.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_fastapi.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_fpdf2.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_functions.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_functools.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_getpass.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_git.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_hashlib.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_http.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_hypothesis.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_ipython.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_iterables.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_jupyter.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_lightweight_charts.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_logging.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_loguru.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_luigi.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_math.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_memory_profiler.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_modules.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_more_itertools.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_numpy.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_operator.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_optuna.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_orjson.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_os.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_parse.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_pathlib.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_period.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_pickle.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_platform.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_polars.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_polars_ols.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_pqdm.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_pydantic.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_pyinstrument.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_pyrsistent.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_pytest.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_pytest_regressions.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_python_dotenv.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_random.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_re.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_redis.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_reprlib.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_rich.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_scipy.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_sentinel.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_shelve.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_socket.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_sqlalchemy.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_sqlalchemy_polars.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_statsmodel.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_streamlit.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_sys.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_tempfile.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_tenacity.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_text.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_threading.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_timer.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_traceback.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_traceback_funcs/__init__.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_traceback_funcs/chain.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_traceback_funcs/error_bind.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_traceback_funcs/many.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_traceback_funcs/one.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_traceback_funcs/recursive.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_traceback_funcs/two.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_traceback_funcs/untraced.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_types.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_typing.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_typing_funcs/__init__.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_typing_funcs/no_future.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_typing_funcs/with_future.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_tzdata.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_tzlocal.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_uuid.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_version.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_warnings.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_whenever.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_zipfile.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/tests/test_zoneinfo.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/altair.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/astor.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/atomicwrites.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/atools.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/cachetools.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/click.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/concurrent.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/contextlib.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/contextvars.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/cryptography.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/cvxpy.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/dataclasses.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/datetime.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/enum.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/eventkit.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/fastapi.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/fpdf2.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/functions.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/functools.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/getpass.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/git.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/hashlib.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/http.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/hypothesis.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/ipython.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/iterables.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/jupyter.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/lightweight_charts.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/logging.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/loguru.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/luigi.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/math.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/memory_profiler.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/modules.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/more_itertools.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/numpy.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/operator.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/optuna.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/orjson.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/os.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/parse.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/pathlib.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/period.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/pickle.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/platform.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/polars.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/polars_ols.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/pqdm.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/py.typed +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/pydantic.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/pyinstrument.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/pyrsistent.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/pytest.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/pytest_regressions.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/python_dotenv.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/random.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/re.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/reprlib.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/rich.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/scipy.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/sentinel.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/shelve.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/slack_sdk.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/socket.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/sqlalchemy.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/sqlalchemy_polars.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/statsmodels.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/streamlit.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/sys.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/tempfile.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/tenacity.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/text.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/threading.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/timer.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/traceback.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/types.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/typing.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/tzdata.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/tzlocal.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/uuid.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/version.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/warnings.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/whenever.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.0}/src/utilities/zipfile.py +0 -0
- {dycw_utilities-0.115.0 → dycw_utilities-0.116.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.116.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.116.0"
|
338
338
|
|
339
339
|
[[tool.bumpversion.files]]
|
340
340
|
filename = "src/utilities/__init__.py"
|
@@ -12,6 +12,7 @@ from asyncio import (
|
|
12
12
|
)
|
13
13
|
from collections import Counter
|
14
14
|
from dataclasses import dataclass, field
|
15
|
+
from functools import partial
|
15
16
|
from itertools import chain, count
|
16
17
|
from re import search
|
17
18
|
from typing import TYPE_CHECKING, Self, override
|
@@ -37,6 +38,7 @@ from utilities.asyncio import (
|
|
37
38
|
InfiniteLooper,
|
38
39
|
InfiniteLooperError,
|
39
40
|
InfiniteQueueLooper,
|
41
|
+
InfiniteQueueLooperError,
|
40
42
|
QueueProcessor,
|
41
43
|
UniquePriorityQueue,
|
42
44
|
UniqueQueue,
|
@@ -58,7 +60,7 @@ from utilities.sentinel import Sentinel, sentinel
|
|
58
60
|
from utilities.timer import Timer
|
59
61
|
|
60
62
|
if TYPE_CHECKING:
|
61
|
-
from collections.abc import Iterator
|
63
|
+
from collections.abc import Callable, Iterator
|
62
64
|
|
63
65
|
from utilities.types import Coroutine1, Duration, MaybeCallableEvent, MaybeType
|
64
66
|
|
@@ -274,12 +276,12 @@ class TestEnhancedTaskGroup:
|
|
274
276
|
|
275
277
|
async def test_timeout_pass(self) -> None:
|
276
278
|
async with EnhancedTaskGroup(timeout=0.2) as tg:
|
277
|
-
_ = tg.create_task(
|
279
|
+
_ = tg.create_task(sleep(0.1))
|
278
280
|
|
279
281
|
async def test_timeout_fail(self) -> None:
|
280
282
|
with raises(ExceptionGroup) as exc_info:
|
281
283
|
async with EnhancedTaskGroup(timeout=0.05) as tg:
|
282
|
-
_ = tg.create_task(
|
284
|
+
_ = tg.create_task(sleep(0.1))
|
283
285
|
assert len(exc_info.value.exceptions) == 1
|
284
286
|
error = one(exc_info.value.exceptions)
|
285
287
|
assert isinstance(error, TimeoutError)
|
@@ -289,7 +291,7 @@ class TestEnhancedTaskGroup:
|
|
289
291
|
|
290
292
|
with raises(ExceptionGroup) as exc_info:
|
291
293
|
async with EnhancedTaskGroup(timeout=0.05, error=CustomError) as tg:
|
292
|
-
_ = tg.create_task(
|
294
|
+
_ = tg.create_task(sleep(0.1))
|
293
295
|
assert len(exc_info.value.exceptions) == 1
|
294
296
|
error = one(exc_info.value.exceptions)
|
295
297
|
assert isinstance(error, CustomError)
|
@@ -376,19 +378,50 @@ class TestInfiniteLooper:
|
|
376
378
|
_ = await looper()
|
377
379
|
|
378
380
|
async def test_hashable(self) -> None:
|
379
|
-
class CustomError(
|
381
|
+
class CustomError(Exception): ...
|
380
382
|
|
381
383
|
@dataclass(kw_only=True, unsafe_hash=True)
|
382
384
|
class Example(InfiniteLooper[None]):
|
385
|
+
@override
|
386
|
+
def _yield_events_and_exceptions(
|
387
|
+
self,
|
388
|
+
) -> Iterator[tuple[None, MaybeType[Exception]]]:
|
389
|
+
yield (None, CustomError)
|
390
|
+
|
391
|
+
looper = Example(sleep_core=0.1)
|
392
|
+
_ = hash(looper)
|
393
|
+
|
394
|
+
async def test_with_coroutine_self_set_event(self) -> None:
|
395
|
+
external: int = 0
|
396
|
+
|
397
|
+
async def inc_external(obj: Example, /) -> None:
|
398
|
+
nonlocal external
|
399
|
+
while True:
|
400
|
+
external += 1
|
401
|
+
obj.counter += 1
|
402
|
+
await sleep(0.05)
|
403
|
+
|
404
|
+
class CustomError(Exception): ...
|
405
|
+
|
406
|
+
@dataclass(kw_only=True)
|
407
|
+
class Example(InfiniteLooper[None]):
|
408
|
+
initializations: int = 0
|
383
409
|
counter: int = 0
|
384
410
|
|
385
411
|
@override
|
386
412
|
async def _initialize(self) -> None:
|
413
|
+
self.initializations += 1
|
387
414
|
self.counter = 0
|
388
415
|
|
389
416
|
@override
|
390
417
|
async def _core(self) -> None:
|
391
418
|
self.counter += 1
|
419
|
+
if self.counter >= 5:
|
420
|
+
self._set_event(None)
|
421
|
+
|
422
|
+
@override
|
423
|
+
def _yield_coroutines(self) -> Iterator[Callable[[], Coroutine1[None]]]:
|
424
|
+
yield partial(inc_external, self)
|
392
425
|
|
393
426
|
@override
|
394
427
|
def _yield_events_and_exceptions(
|
@@ -396,68 +429,95 @@ class TestInfiniteLooper:
|
|
396
429
|
) -> Iterator[tuple[None, MaybeType[BaseException]]]:
|
397
430
|
yield (None, CustomError)
|
398
431
|
|
399
|
-
looper = Example(sleep_core=0.
|
400
|
-
|
432
|
+
looper = Example(sleep_core=0.05, sleep_restart=0.05)
|
433
|
+
with raises(TimeoutError):
|
434
|
+
async with timeout_dur(duration=1.0):
|
435
|
+
await looper()
|
436
|
+
assert 4 <= looper.initializations <= 6
|
437
|
+
assert 0 <= looper.counter <= 7
|
438
|
+
assert 17 <= external <= 21
|
401
439
|
|
402
|
-
async def
|
403
|
-
class
|
440
|
+
async def test_with_coroutine_self_error(self) -> None:
|
441
|
+
class CustomError(Exception): ...
|
442
|
+
|
443
|
+
async def dummy() -> None:
|
444
|
+
_ = await Event().wait()
|
404
445
|
|
405
446
|
@dataclass(kw_only=True)
|
406
|
-
class
|
447
|
+
class Example(InfiniteLooper[None]):
|
448
|
+
initializations: int = 0
|
407
449
|
counter: int = 0
|
408
450
|
|
409
451
|
@override
|
410
452
|
async def _initialize(self) -> None:
|
453
|
+
self.initializations += 1
|
411
454
|
self.counter = 0
|
412
455
|
|
413
456
|
@override
|
414
457
|
async def _core(self) -> None:
|
415
458
|
self.counter += 1
|
416
|
-
if self.counter >=
|
417
|
-
|
459
|
+
if self.counter >= 5:
|
460
|
+
raise CustomError
|
461
|
+
|
462
|
+
@override
|
463
|
+
def _yield_coroutines(self) -> Iterator[Callable[[], Coroutine1[None]]]:
|
464
|
+
yield dummy
|
418
465
|
|
419
466
|
@override
|
420
467
|
def _yield_events_and_exceptions(
|
421
468
|
self,
|
422
469
|
) -> Iterator[tuple[None, MaybeType[BaseException]]]:
|
423
|
-
yield (None,
|
470
|
+
yield (None, CustomError)
|
471
|
+
|
472
|
+
looper = Example(sleep_core=0.05, sleep_restart=0.05)
|
473
|
+
with raises(TimeoutError):
|
474
|
+
async with timeout_dur(duration=1.0):
|
475
|
+
await looper()
|
476
|
+
assert 3 <= looper.initializations <= 5
|
477
|
+
assert 0 <= looper.counter <= 5
|
424
478
|
|
425
|
-
|
479
|
+
@given(logger=just("logger") | none())
|
480
|
+
async def test_with_coroutine_other_coroutine_error(
|
481
|
+
self, *, logger: str | None
|
482
|
+
) -> None:
|
483
|
+
class CustomError(Exception): ...
|
484
|
+
|
485
|
+
async def dummy() -> None:
|
486
|
+
for i in count():
|
487
|
+
if i >= 5:
|
488
|
+
raise CustomError
|
489
|
+
await sleep(0.05)
|
426
490
|
|
427
491
|
@dataclass(kw_only=True)
|
428
|
-
class
|
492
|
+
class Example(InfiniteLooper[None]):
|
493
|
+
initializations: int = 0
|
429
494
|
counter: int = 0
|
430
|
-
child: Child
|
431
495
|
|
432
496
|
@override
|
433
497
|
async def _initialize(self) -> None:
|
498
|
+
self.initializations += 1
|
434
499
|
self.counter = 0
|
435
500
|
|
436
501
|
@override
|
437
502
|
async def _core(self) -> None:
|
438
503
|
self.counter += 1
|
439
|
-
self.child.counter += 1
|
440
|
-
if self.counter >= 10:
|
441
|
-
self._set_event(None)
|
442
504
|
|
443
505
|
@override
|
444
|
-
def _yield_coroutines(self) -> Iterator[Coroutine1[None]]:
|
445
|
-
yield
|
506
|
+
def _yield_coroutines(self) -> Iterator[Callable[[], Coroutine1[None]]]:
|
507
|
+
yield dummy
|
446
508
|
|
447
509
|
@override
|
448
510
|
def _yield_events_and_exceptions(
|
449
511
|
self,
|
450
512
|
) -> Iterator[tuple[None, MaybeType[BaseException]]]:
|
451
|
-
yield (None,
|
513
|
+
yield (None, CustomError)
|
452
514
|
|
453
|
-
|
454
|
-
with raises(
|
455
|
-
async with timeout_dur(duration=1.
|
456
|
-
await
|
457
|
-
|
458
|
-
assert
|
459
|
-
assert 10 <= parent.child.counter <= 15
|
460
|
-
assert 3 <= parent.counter <= 7
|
515
|
+
looper = Example(sleep_core=0.05, sleep_restart=0.05, logger=logger)
|
516
|
+
with raises(CancelledError):
|
517
|
+
async with timeout_dur(duration=1.0):
|
518
|
+
await looper()
|
519
|
+
assert 3 <= looper.initializations <= 5
|
520
|
+
assert 1 <= looper.counter <= 5
|
461
521
|
|
462
522
|
@given(logger=just("logger") | none())
|
463
523
|
async def test_error_upon_initialize(self, *, logger: str | None) -> None:
|
@@ -473,12 +533,13 @@ class TestInfiniteLooper:
|
|
473
533
|
async def _core(self) -> None:
|
474
534
|
raise NotImplementedError
|
475
535
|
|
536
|
+
looper = Example(sleep_core=0.1, logger=logger)
|
476
537
|
with raises(TimeoutError):
|
477
538
|
async with timeout_dur(duration=0.5):
|
478
|
-
_ = await
|
539
|
+
_ = await looper()
|
479
540
|
|
480
541
|
@given(logger=just("logger") | none())
|
481
|
-
async def
|
542
|
+
async def test_error_group_upon_coroutines(self, *, logger: str | None) -> None:
|
482
543
|
class CustomError(Exception): ...
|
483
544
|
|
484
545
|
@dataclass(kw_only=True)
|
@@ -493,9 +554,10 @@ class TestInfiniteLooper:
|
|
493
554
|
) -> Iterator[tuple[None, MaybeType[BaseException]]]:
|
494
555
|
yield (None, CustomError)
|
495
556
|
|
557
|
+
looper = Example(sleep_core=0.1, logger=logger)
|
496
558
|
with raises(TimeoutError):
|
497
559
|
async with timeout_dur(duration=0.5):
|
498
|
-
_ = await
|
560
|
+
_ = await looper()
|
499
561
|
|
500
562
|
async def test_error_no_event_found(self) -> None:
|
501
563
|
@dataclass(kw_only=True)
|
@@ -513,7 +575,7 @@ class TestInfiniteLooper:
|
|
513
575
|
self._set_event(None)
|
514
576
|
|
515
577
|
looper = Example(sleep_core=0.1)
|
516
|
-
with raises(InfiniteLooperError, match="
|
578
|
+
with raises(InfiniteLooperError, match="'Example' does not have an event None"):
|
517
579
|
_ = await looper()
|
518
580
|
|
519
581
|
|
@@ -527,18 +589,18 @@ class TestInfiniteQueueLooper:
|
|
527
589
|
async def _process_items(self, *items: int) -> None:
|
528
590
|
self.output.update(items)
|
529
591
|
|
530
|
-
|
592
|
+
looper = Example(sleep_core=0.05)
|
531
593
|
|
532
594
|
async def add_items() -> None:
|
533
595
|
for i in count():
|
534
|
-
|
535
|
-
await sleep(0.
|
596
|
+
looper.put_items_nowait(i)
|
597
|
+
await sleep(0.05)
|
536
598
|
|
537
599
|
with raises(ExceptionGroup): # noqa: PT012
|
538
600
|
async with EnhancedTaskGroup(timeout=1.0) as tg:
|
539
|
-
_ = tg.create_task(
|
601
|
+
_ = tg.create_task(looper())
|
540
602
|
_ = tg.create_task(add_items())
|
541
|
-
assert
|
603
|
+
assert 15 <= len(looper.output) <= 20
|
542
604
|
|
543
605
|
async def test_no_items(self) -> None:
|
544
606
|
@dataclass(kw_only=True)
|
@@ -549,12 +611,13 @@ class TestInfiniteQueueLooper:
|
|
549
611
|
async def _process_items(self, *items: int) -> None:
|
550
612
|
self.output.update(items)
|
551
613
|
|
552
|
-
|
614
|
+
looper = Example(sleep_core=0.05)
|
553
615
|
with raises(TimeoutError):
|
554
616
|
async with timeout_dur(duration=0.5):
|
555
|
-
_ = await
|
617
|
+
_ = await looper()
|
556
618
|
|
557
|
-
|
619
|
+
@given(logger=just("logger") | none())
|
620
|
+
async def test_error_process_items(self, *, logger: str | None) -> None:
|
558
621
|
class CustomError(Exception): ...
|
559
622
|
|
560
623
|
@dataclass(kw_only=True)
|
@@ -565,11 +628,28 @@ class TestInfiniteQueueLooper:
|
|
565
628
|
async def _process_items(self, *items: int) -> None:
|
566
629
|
raise CustomError(*items)
|
567
630
|
|
568
|
-
|
569
|
-
|
631
|
+
looper = Example(sleep_core=0.05, logger=logger)
|
632
|
+
looper.put_items_nowait(1)
|
570
633
|
with raises(TimeoutError):
|
571
634
|
async with timeout_dur(duration=0.5):
|
572
|
-
_ = await
|
635
|
+
_ = await looper()
|
636
|
+
|
637
|
+
async def test_error_infinite_queue_looper(self) -> None:
|
638
|
+
class CustomError(Exception): ...
|
639
|
+
|
640
|
+
@dataclass(kw_only=True)
|
641
|
+
class Example(InfiniteQueueLooper[None, int]):
|
642
|
+
@override
|
643
|
+
async def _process_items(self, *items: int) -> None:
|
644
|
+
raise CustomError(*items)
|
645
|
+
|
646
|
+
looper = Example(sleep_core=0.1)
|
647
|
+
looper.put_items_nowait(1)
|
648
|
+
with raises(
|
649
|
+
InfiniteQueueLooperError,
|
650
|
+
match=r"'Example' encountered CustomError\(1\) whilst processing 1 item\(s\): \[1\]",
|
651
|
+
):
|
652
|
+
_ = await looper._core()
|
573
653
|
|
574
654
|
|
575
655
|
class TestPutAndGetItems:
|
@@ -852,19 +932,19 @@ class TestStreamCommand:
|
|
852
932
|
class TestTimeoutDur:
|
853
933
|
async def test_pass(self) -> None:
|
854
934
|
async with timeout_dur(duration=0.2):
|
855
|
-
await
|
935
|
+
await sleep(0.1)
|
856
936
|
|
857
937
|
async def test_fail(self) -> None:
|
858
938
|
with raises(TimeoutError):
|
859
939
|
async with timeout_dur(duration=0.05):
|
860
|
-
await
|
940
|
+
await sleep(0.1)
|
861
941
|
|
862
942
|
async def test_custom_error(self) -> None:
|
863
943
|
class CustomError(Exception): ...
|
864
944
|
|
865
945
|
with raises(CustomError):
|
866
946
|
async with timeout_dur(duration=0.05, error=CustomError):
|
867
|
-
await
|
947
|
+
await sleep(0.1)
|
868
948
|
|
869
949
|
|
870
950
|
if __name__ == "__main__":
|
@@ -0,0 +1,28 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from pytest import raises
|
4
|
+
|
5
|
+
from utilities.errors import ImpossibleCaseError, repr_error
|
6
|
+
|
7
|
+
|
8
|
+
class TestImpossibleCaseError:
|
9
|
+
def test_main(self) -> None:
|
10
|
+
x = None
|
11
|
+
with raises(ImpossibleCaseError, match=r"Case must be possible: x=None\."):
|
12
|
+
raise ImpossibleCaseError(case=[f"{x=}"])
|
13
|
+
|
14
|
+
|
15
|
+
class TestReprError:
|
16
|
+
def test_class(self) -> None:
|
17
|
+
class CustomError(Exception): ...
|
18
|
+
|
19
|
+
result = repr_error(CustomError)
|
20
|
+
expected = "CustomError"
|
21
|
+
assert result == expected
|
22
|
+
|
23
|
+
def test_instance(self) -> None:
|
24
|
+
class CustomError(Exception): ...
|
25
|
+
|
26
|
+
result = repr_error(CustomError())
|
27
|
+
expected = "CustomError()"
|
28
|
+
assert result == expected
|
@@ -155,10 +155,12 @@ class TestSlackHandlerIQL:
|
|
155
155
|
messages.append(text)
|
156
156
|
|
157
157
|
logger = getLogger(str(tmp_path))
|
158
|
-
logger.addHandler(
|
158
|
+
logger.addHandler(
|
159
|
+
handler := SlackHandlerIQL("url", sleep_core=0.05, sender=sender)
|
160
|
+
)
|
159
161
|
|
160
162
|
async def sleep_then_log() -> None:
|
161
|
-
await sleep_dur(duration=0.
|
163
|
+
await sleep_dur(duration=0.05)
|
162
164
|
logger.warning("message")
|
163
165
|
|
164
166
|
with raises(ExceptionGroup): # noqa: PT012
|
@@ -173,10 +175,10 @@ class TestSlackHandlerIQL:
|
|
173
175
|
async def test_real(self, *, tmp_path: Path) -> None:
|
174
176
|
url = get_env_var("SLACK")
|
175
177
|
logger = getLogger(str(tmp_path))
|
176
|
-
logger.addHandler(handler := SlackHandlerIQL(url))
|
178
|
+
logger.addHandler(handler := SlackHandlerIQL(url, sleep_core=0.05))
|
177
179
|
|
178
180
|
async def sleep_then_log() -> None:
|
179
|
-
await sleep_dur(duration=0.
|
181
|
+
await sleep_dur(duration=0.05)
|
180
182
|
for i in range(10):
|
181
183
|
logger.warning(
|
182
184
|
"message %d from %s", i, TestSlackHandlerIQL.test_real.__qualname__
|
@@ -30,6 +30,7 @@ from subprocess import PIPE
|
|
30
30
|
from sys import stderr, stdout
|
31
31
|
from typing import (
|
32
32
|
TYPE_CHECKING,
|
33
|
+
Any,
|
33
34
|
Generic,
|
34
35
|
NoReturn,
|
35
36
|
Self,
|
@@ -41,8 +42,9 @@ from typing import (
|
|
41
42
|
)
|
42
43
|
|
43
44
|
from utilities.datetime import MILLISECOND, MINUTE, SECOND, datetime_duration_to_float
|
44
|
-
from utilities.errors import ImpossibleCaseError
|
45
|
+
from utilities.errors import ImpossibleCaseError, repr_error
|
45
46
|
from utilities.functions import ensure_int, ensure_not_none, get_class_name
|
47
|
+
from utilities.reprlib import get_repr
|
46
48
|
from utilities.sentinel import Sentinel, sentinel
|
47
49
|
from utilities.types import (
|
48
50
|
Coroutine1,
|
@@ -379,17 +381,19 @@ class InfiniteLooper(ABC, Generic[THashable]):
|
|
379
381
|
self._error_upon_core(error)
|
380
382
|
await sleep_dur(duration=self.sleep_restart)
|
381
383
|
|
382
|
-
async def _run_looper_with_coroutines(
|
384
|
+
async def _run_looper_with_coroutines(
|
385
|
+
self, *coroutines: Callable[[], Coroutine1[None]]
|
386
|
+
) -> None:
|
383
387
|
"""Run multiple loopers."""
|
384
388
|
while True:
|
385
389
|
self._reset_events()
|
386
390
|
try:
|
387
391
|
async with TaskGroup() as tg:
|
388
392
|
_ = tg.create_task(self._run_looper())
|
389
|
-
_ =
|
390
|
-
except
|
391
|
-
self.
|
392
|
-
await sleep_dur(duration=self.sleep_restart)
|
393
|
+
_ = [tg.create_task(c()) for c in coroutines]
|
394
|
+
except ExceptionGroup as error:
|
395
|
+
self._error_group_upon_coroutines(error)
|
396
|
+
await sleep_dur(duration=self.sleep_restart)
|
393
397
|
|
394
398
|
async def _initialize(self) -> None:
|
395
399
|
"""Initialize the loop."""
|
@@ -401,9 +405,9 @@ class InfiniteLooper(ABC, Generic[THashable]):
|
|
401
405
|
"""Handle any errors upon initializing the looper."""
|
402
406
|
if self.logger is not None:
|
403
407
|
getLogger(name=self.logger).error(
|
404
|
-
"
|
408
|
+
"%r encountered %r whilst initializing; sleeping for %s...",
|
405
409
|
get_class_name(self),
|
406
|
-
|
410
|
+
repr_error(error),
|
407
411
|
self.sleep_restart,
|
408
412
|
)
|
409
413
|
|
@@ -411,12 +415,25 @@ class InfiniteLooper(ABC, Generic[THashable]):
|
|
411
415
|
"""Handle any errors upon running the core function."""
|
412
416
|
if self.logger is not None:
|
413
417
|
getLogger(name=self.logger).error(
|
414
|
-
"
|
418
|
+
"%r encountered %r; sleeping for %s...",
|
415
419
|
get_class_name(self),
|
416
|
-
|
420
|
+
repr_error(error),
|
417
421
|
self.sleep_restart,
|
418
422
|
)
|
419
423
|
|
424
|
+
def _error_group_upon_coroutines(self, group: ExceptionGroup, /) -> None:
|
425
|
+
"""Handle any errors upon running the core function."""
|
426
|
+
if self.logger is not None:
|
427
|
+
errors = group.exceptions
|
428
|
+
n = len(errors)
|
429
|
+
msgs = [f"{get_class_name(self)!r} encountered {n} error(s):"]
|
430
|
+
msgs.extend(
|
431
|
+
f"- Error #{i}/{n}: {repr_error(e)}"
|
432
|
+
for i, e in enumerate(errors, start=1)
|
433
|
+
)
|
434
|
+
msgs.append(f"Sleeping for {self.sleep_restart}...")
|
435
|
+
getLogger(name=self.logger).error("\n".join(msgs))
|
436
|
+
|
420
437
|
def _raise_error(self, event: THashable, /) -> NoReturn:
|
421
438
|
"""Raise the error corresponding to given event."""
|
422
439
|
mapping = dict(self._yield_events_and_exceptions())
|
@@ -434,10 +451,10 @@ class InfiniteLooper(ABC, Generic[THashable]):
|
|
434
451
|
try:
|
435
452
|
event_obj = self._events[event]
|
436
453
|
except KeyError:
|
437
|
-
raise InfiniteLooperError(event=event) from None
|
454
|
+
raise InfiniteLooperError(looper=self, event=event) from None
|
438
455
|
event_obj.set()
|
439
456
|
|
440
|
-
def _yield_coroutines(self) -> Iterator[Coroutine1[None]]:
|
457
|
+
def _yield_coroutines(self) -> Iterator[Callable[[], Coroutine1[None]]]:
|
441
458
|
"""Yield any other coroutines which must also be run."""
|
442
459
|
yield from []
|
443
460
|
|
@@ -450,11 +467,12 @@ class InfiniteLooper(ABC, Generic[THashable]):
|
|
450
467
|
|
451
468
|
@dataclass(kw_only=True, slots=True)
|
452
469
|
class InfiniteLooperError(Exception):
|
470
|
+
looper: InfiniteLooper[Any]
|
453
471
|
event: Hashable
|
454
472
|
|
455
473
|
@override
|
456
474
|
def __str__(self) -> str:
|
457
|
-
return f"
|
475
|
+
return f"{get_class_name(self.looper)!r} does not have an event {self.event!r}"
|
458
476
|
|
459
477
|
|
460
478
|
##
|
@@ -466,25 +484,24 @@ class InfiniteQueueLooper(InfiniteLooper[THashable], Generic[THashable, _T]):
|
|
466
484
|
|
467
485
|
queue_type: type[Queue[_T]] = field(default=Queue, repr=False)
|
468
486
|
_queue: Queue[_T] = field(init=False)
|
469
|
-
_current: Queue[_T] = field(init=False)
|
470
487
|
|
471
488
|
@override
|
472
489
|
def __post_init__(self) -> None:
|
473
490
|
super().__post_init__()
|
474
491
|
self._queue = self.queue_type()
|
475
|
-
self._current = self.queue_type()
|
476
492
|
|
477
493
|
@override
|
478
494
|
async def _core(self) -> None:
|
479
495
|
"""Run the core part of the loop."""
|
480
|
-
items =
|
481
|
-
|
482
|
-
|
496
|
+
items = get_items_nowait(self._queue)
|
497
|
+
if len(items) == 0:
|
498
|
+
return
|
483
499
|
try:
|
484
500
|
await self._process_items(*items)
|
485
|
-
except Exception:
|
486
|
-
|
487
|
-
|
501
|
+
except Exception as error: # noqa: BLE001
|
502
|
+
raise InfiniteQueueLooperError(
|
503
|
+
looper=self, items=items, error=error
|
504
|
+
) from None
|
488
505
|
|
489
506
|
@abstractmethod
|
490
507
|
async def _process_items(self, *items: _T) -> None:
|
@@ -494,6 +511,33 @@ class InfiniteQueueLooper(InfiniteLooper[THashable], Generic[THashable, _T]):
|
|
494
511
|
"""Put items into the queue."""
|
495
512
|
put_items_nowait(items, self._queue)
|
496
513
|
|
514
|
+
@override
|
515
|
+
def _error_upon_core(self, error: Exception, /) -> None:
|
516
|
+
"""Handle any errors upon running the core function."""
|
517
|
+
if self.logger is not None:
|
518
|
+
if isinstance(error, InfiniteQueueLooperError):
|
519
|
+
getLogger(name=self.logger).error(
|
520
|
+
"%r encountered %s whilst processing %d item(s) %s; sleeping for %s...",
|
521
|
+
get_class_name(self),
|
522
|
+
repr_error(error.error),
|
523
|
+
len(error.items),
|
524
|
+
get_repr(error.items),
|
525
|
+
self.sleep_restart,
|
526
|
+
)
|
527
|
+
else:
|
528
|
+
super()._error_upon_core(error) # pragma: no cover
|
529
|
+
|
530
|
+
|
531
|
+
@dataclass(kw_only=True, slots=True)
|
532
|
+
class InfiniteQueueLooperError(Exception, Generic[_T]):
|
533
|
+
looper: InfiniteQueueLooper[Any, Any]
|
534
|
+
items: Sequence[_T]
|
535
|
+
error: Exception
|
536
|
+
|
537
|
+
@override
|
538
|
+
def __str__(self) -> str:
|
539
|
+
return f"{get_class_name(self.looper)!r} encountered {repr_error(self.error)} whilst processing {len(self.items)} item(s): {get_repr(self.items)}"
|
540
|
+
|
497
541
|
|
498
542
|
##
|
499
543
|
|
@@ -703,6 +747,7 @@ __all__ = [
|
|
703
747
|
"InfiniteLooper",
|
704
748
|
"InfiniteLooperError",
|
705
749
|
"InfiniteQueueLooper",
|
750
|
+
"InfiniteQueueLooperError",
|
706
751
|
"QueueProcessor",
|
707
752
|
"StreamCommandOutput",
|
708
753
|
"UniquePriorityQueue",
|
@@ -0,0 +1,34 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from typing import TYPE_CHECKING, assert_never, override
|
5
|
+
|
6
|
+
if TYPE_CHECKING:
|
7
|
+
from utilities.types import MaybeType
|
8
|
+
|
9
|
+
|
10
|
+
@dataclass(kw_only=True, slots=True)
|
11
|
+
class ImpossibleCaseError(Exception):
|
12
|
+
case: list[str]
|
13
|
+
|
14
|
+
@override
|
15
|
+
def __str__(self) -> str:
|
16
|
+
desc = ", ".join(self.case)
|
17
|
+
return f"Case must be possible: {desc}."
|
18
|
+
|
19
|
+
|
20
|
+
##
|
21
|
+
|
22
|
+
|
23
|
+
def repr_error(error: MaybeType[Exception], /) -> str:
|
24
|
+
"""Get a string representation of an error."""
|
25
|
+
match error:
|
26
|
+
case Exception() as error_obj:
|
27
|
+
return f"{error_obj.__class__.__name__}({error_obj})"
|
28
|
+
case type() as error_cls:
|
29
|
+
return error_cls.__name__
|
30
|
+
case _ as never:
|
31
|
+
assert_never(never)
|
32
|
+
|
33
|
+
|
34
|
+
__all__ = ["ImpossibleCaseError", "repr_error"]
|