dycw-utilities 0.116.6__tar.gz → 0.117.1__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.116.6 → dycw_utilities-0.117.1}/PKG-INFO +1 -1
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/pyproject.toml +2 -2
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_asyncio.py +73 -10
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_datetime.py +56 -56
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/__init__.py +1 -1
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/asyncio.py +71 -12
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/datetime.py +58 -55
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/whenever.py +2 -2
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/.gitignore +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/LICENSE +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/README.md +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/__init__.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/conftest.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/modules/__init__.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/modules/package_missing/__init__.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/modules/package_missing/module.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/modules/package_with/__init__.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/modules/package_with/outer_1.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/modules/package_with/outer_2.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/modules/package_with/subpackage/__init__.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/modules/package_with/subpackage/inner_1.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/modules/package_with/subpackage/inner_2.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/modules/package_with/subpackage/inner_3.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/modules/package_without/__init__.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/modules/package_without/module_1.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/modules/package_without/module_2.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/modules/standalone.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/modules/with_imports.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__obj.json +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/regressions/test_pytest_regressions/TestMultipleRegressionFixtures__test_main__series.json +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_int.json +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__false.json +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_literal__true.json +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/regressions/test_pytest_regressions/TestOrjsonRegressionFixture__test_dataclass_nested.json +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_dataframe.json +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/regressions/test_pytest_regressions/TestPolarsRegressionFixture__test_series.json +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/scripts/__init__.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/scripts/test_async_service/__init__.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/scripts/test_async_service/__main__.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/scripts/test_async_service/run.sh +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/scripts/test_queue_processor/__init__.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/scripts/test_queue_processor/__main__.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/scripts/test_queue_processor/run.sh +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_altair.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_astor.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_atomicwrites.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_atools.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_cachetools.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_click.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_concurrent.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_contextlib.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_contextvars.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_cryptography.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_cvxpy.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_dataclasses.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_enum.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_errors.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_eventkit.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_fastapi.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_fpdf2.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_functions.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_functools.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_getpass.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_git.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_hashlib.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_http.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_hypothesis.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_importlib.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_ipython.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_iterables.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_jupyter.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_lightweight_charts.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_logging.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_loguru.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_luigi.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_math.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_memory_profiler.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_modules.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_more_itertools.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_numpy.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_operator.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_optuna.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_orjson.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_os.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_parse.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_pathlib.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_period.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_pickle.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_platform.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_polars.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_polars_ols.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_pqdm.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_pydantic.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_pyinstrument.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_pyrsistent.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_pytest.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_pytest_regressions.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_python_dotenv.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_random.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_re.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_redis.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_reprlib.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_rich.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_scipy.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_sentinel.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_shelve.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_slack_sdk.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_socket.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_sqlalchemy.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_sqlalchemy_polars.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_statsmodel.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_streamlit.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_sys.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_tempfile.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_tenacity.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_text.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_threading.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_timer.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_traceback.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_traceback_funcs/__init__.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_traceback_funcs/chain.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_traceback_funcs/decorated_async.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_traceback_funcs/decorated_sync.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_traceback_funcs/error_bind.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_traceback_funcs/many.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_traceback_funcs/one.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_traceback_funcs/recursive.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_traceback_funcs/task_group_one.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_traceback_funcs/task_group_two.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_traceback_funcs/two.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_traceback_funcs/untraced.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_types.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_typing.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_typing_funcs/__init__.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_typing_funcs/no_future.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_typing_funcs/with_future.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_tzdata.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_tzlocal.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_uuid.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_version.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_warnings.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_whenever.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_zipfile.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/tests/test_zoneinfo.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/altair.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/astor.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/atomicwrites.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/atools.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/cachetools.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/click.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/concurrent.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/contextlib.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/contextvars.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/cryptography.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/cvxpy.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/dataclasses.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/enum.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/errors.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/eventkit.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/fastapi.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/fpdf2.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/functions.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/functools.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/getpass.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/git.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/hashlib.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/http.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/hypothesis.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/importlib.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/ipython.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/iterables.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/jupyter.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/lightweight_charts.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/logging.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/loguru.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/luigi.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/math.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/memory_profiler.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/modules.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/more_itertools.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/numpy.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/operator.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/optuna.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/orjson.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/os.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/parse.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/pathlib.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/period.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/pickle.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/platform.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/polars.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/polars_ols.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/pqdm.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/py.typed +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/pydantic.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/pyinstrument.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/pyrsistent.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/pytest.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/pytest_regressions.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/python_dotenv.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/random.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/re.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/redis.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/reprlib.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/rich.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/scipy.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/sentinel.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/shelve.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/slack_sdk.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/socket.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/sqlalchemy.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/sqlalchemy_polars.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/statsmodels.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/streamlit.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/sys.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/tempfile.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/tenacity.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/text.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/threading.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/timer.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/traceback.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/types.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/typing.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/tzdata.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/tzlocal.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/uuid.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/version.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/warnings.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/src/utilities/zipfile.py +0 -0
- {dycw_utilities-0.116.6 → dycw_utilities-0.117.1}/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.117.1"
|
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.117.1"
|
338
338
|
|
339
339
|
[[tool.bumpversion.files]]
|
340
340
|
filename = "src/utilities/__init__.py"
|
@@ -17,7 +17,7 @@ from itertools import chain, count
|
|
17
17
|
from re import search
|
18
18
|
from typing import TYPE_CHECKING, Self, override
|
19
19
|
|
20
|
-
from hypothesis import Phase, given, settings
|
20
|
+
from hypothesis import HealthCheck, Phase, given, settings
|
21
21
|
from hypothesis.strategies import (
|
22
22
|
DataObject,
|
23
23
|
data,
|
@@ -28,7 +28,7 @@ from hypothesis.strategies import (
|
|
28
28
|
permutations,
|
29
29
|
sampled_from,
|
30
30
|
)
|
31
|
-
from pytest import approx, mark, param, raises
|
31
|
+
from pytest import LogCaptureFixture, approx, mark, param, raises
|
32
32
|
|
33
33
|
from utilities.asyncio import (
|
34
34
|
AsyncLoopingService,
|
@@ -42,17 +42,25 @@ from utilities.asyncio import (
|
|
42
42
|
QueueProcessor,
|
43
43
|
UniquePriorityQueue,
|
44
44
|
UniqueQueue,
|
45
|
+
_DurationOrEvery,
|
45
46
|
get_event,
|
46
47
|
get_items,
|
47
48
|
get_items_nowait,
|
48
49
|
put_items,
|
49
50
|
put_items_nowait,
|
50
51
|
sleep_dur,
|
52
|
+
sleep_until,
|
53
|
+
sleep_until_rounded,
|
51
54
|
stream_command,
|
52
55
|
timeout_dur,
|
53
56
|
)
|
54
57
|
from utilities.dataclasses import replace_non_sentinel
|
55
|
-
from utilities.datetime import
|
58
|
+
from utilities.datetime import (
|
59
|
+
MILLISECOND,
|
60
|
+
MINUTE,
|
61
|
+
datetime_duration_to_timedelta,
|
62
|
+
get_now,
|
63
|
+
)
|
56
64
|
from utilities.hypothesis import sentinels, text_ascii
|
57
65
|
from utilities.iterables import one, unique_everseen
|
58
66
|
from utilities.pytest import skipif_windows
|
@@ -341,8 +349,8 @@ class TestGetEvent:
|
|
341
349
|
|
342
350
|
|
343
351
|
class TestInfiniteLooper:
|
344
|
-
@given(n=integers(10, 11))
|
345
|
-
async def test_main(self, *, n: int) -> None:
|
352
|
+
@given(n=integers(10, 11), sleep_core=sampled_from([0.1, ("every", 0.1)]))
|
353
|
+
async def test_main(self, *, n: int, sleep_core: _DurationOrEvery) -> None:
|
346
354
|
class TrueError(BaseException): ...
|
347
355
|
|
348
356
|
class FalseError(BaseException): ...
|
@@ -368,7 +376,7 @@ class TestInfiniteLooper:
|
|
368
376
|
yield (True, TrueError)
|
369
377
|
yield (False, FalseError)
|
370
378
|
|
371
|
-
looper = Example(sleep_core=
|
379
|
+
looper = Example(sleep_core=sleep_core)
|
372
380
|
match n % 2 == 0:
|
373
381
|
case True:
|
374
382
|
with raises(TrueError):
|
@@ -520,7 +528,24 @@ class TestInfiniteLooper:
|
|
520
528
|
assert 1 <= looper.counter <= 6
|
521
529
|
|
522
530
|
@given(logger=just("logger") | none())
|
523
|
-
|
531
|
+
@mark.parametrize(
|
532
|
+
("sleep_restart", "desc"),
|
533
|
+
[
|
534
|
+
(60.0, "for 0:01:00"),
|
535
|
+
(MINUTE, "for 0:01:00"),
|
536
|
+
(("every", 60), "until next 0:01:00"),
|
537
|
+
(("every", MINUTE), "until next 0:01:00"),
|
538
|
+
],
|
539
|
+
)
|
540
|
+
@settings(suppress_health_check={HealthCheck.function_scoped_fixture})
|
541
|
+
async def test_error_upon_initialize(
|
542
|
+
self,
|
543
|
+
*,
|
544
|
+
sleep_restart: _DurationOrEvery,
|
545
|
+
desc: str,
|
546
|
+
logger: str | None,
|
547
|
+
caplog: LogCaptureFixture,
|
548
|
+
) -> None:
|
524
549
|
class CustomError(Exception): ...
|
525
550
|
|
526
551
|
@dataclass(kw_only=True)
|
@@ -533,13 +558,34 @@ class TestInfiniteLooper:
|
|
533
558
|
async def _core(self) -> None:
|
534
559
|
raise NotImplementedError
|
535
560
|
|
536
|
-
looper = Example(sleep_core=0.1, logger=logger)
|
561
|
+
looper = Example(sleep_core=0.1, sleep_restart=sleep_restart, logger=logger)
|
537
562
|
with raises(TimeoutError):
|
538
563
|
async with timeout_dur(duration=0.5):
|
539
564
|
_ = await looper()
|
565
|
+
if logger is not None:
|
566
|
+
message = caplog.messages[0]
|
567
|
+
expected = f"'Example' encountered 'CustomError()' whilst initializing; sleeping {desc}..."
|
568
|
+
assert message == expected
|
540
569
|
|
541
570
|
@given(logger=just("logger") | none())
|
542
|
-
|
571
|
+
@mark.parametrize(
|
572
|
+
("sleep_restart", "desc"),
|
573
|
+
[
|
574
|
+
(60.0, "for 0:01:00"),
|
575
|
+
(MINUTE, "for 0:01:00"),
|
576
|
+
(("every", 60), "until next 0:01:00"),
|
577
|
+
(("every", MINUTE), "until next 0:01:00"),
|
578
|
+
],
|
579
|
+
)
|
580
|
+
@settings(suppress_health_check={HealthCheck.function_scoped_fixture})
|
581
|
+
async def test_error_group_upon_coroutines(
|
582
|
+
self,
|
583
|
+
*,
|
584
|
+
sleep_restart: _DurationOrEvery,
|
585
|
+
desc: str,
|
586
|
+
logger: str | None,
|
587
|
+
caplog: LogCaptureFixture,
|
588
|
+
) -> None:
|
543
589
|
class CustomError(Exception): ...
|
544
590
|
|
545
591
|
@dataclass(kw_only=True)
|
@@ -554,10 +600,14 @@ class TestInfiniteLooper:
|
|
554
600
|
) -> Iterator[tuple[None, MaybeType[BaseException]]]:
|
555
601
|
yield (None, CustomError)
|
556
602
|
|
557
|
-
looper = Example(sleep_core=0.1, logger=logger)
|
603
|
+
looper = Example(sleep_core=0.1, sleep_restart=sleep_restart, logger=logger)
|
558
604
|
with raises(TimeoutError):
|
559
605
|
async with timeout_dur(duration=0.5):
|
560
606
|
_ = await looper()
|
607
|
+
if logger is not None:
|
608
|
+
message = caplog.messages[0]
|
609
|
+
expected = f"'Example' encountered 'CustomError()'; sleeping {desc}..."
|
610
|
+
assert message == expected
|
561
611
|
|
562
612
|
async def test_error_no_event_found(self) -> None:
|
563
613
|
@dataclass(kw_only=True)
|
@@ -951,6 +1001,19 @@ class TestSleepDur:
|
|
951
1001
|
assert timer <= 0.01
|
952
1002
|
|
953
1003
|
|
1004
|
+
class TestSleepUntil:
|
1005
|
+
async def test_main(self) -> None:
|
1006
|
+
now = get_now()
|
1007
|
+
with Timer() as timer:
|
1008
|
+
await sleep_until(now + 10 * MILLISECOND)
|
1009
|
+
assert timer >= datetime_duration_to_timedelta(5 * MILLISECOND)
|
1010
|
+
|
1011
|
+
|
1012
|
+
class TestSleepUntilRounded:
|
1013
|
+
async def test_main(self) -> None:
|
1014
|
+
await sleep_until_rounded(10 * MILLISECOND)
|
1015
|
+
|
1016
|
+
|
954
1017
|
class TestStreamCommand:
|
955
1018
|
@skipif_windows
|
956
1019
|
async def test_main(self) -> None:
|
@@ -88,6 +88,8 @@ from utilities.datetime import (
|
|
88
88
|
date_to_datetime,
|
89
89
|
date_to_month,
|
90
90
|
datetime_duration_to_float,
|
91
|
+
datetime_duration_to_microseconds,
|
92
|
+
datetime_duration_to_milliseconds,
|
91
93
|
datetime_duration_to_timedelta,
|
92
94
|
datetime_utc,
|
93
95
|
days_since_epoch,
|
@@ -128,8 +130,6 @@ from utilities.datetime import (
|
|
128
130
|
serialize_month,
|
129
131
|
sub_duration,
|
130
132
|
timedelta_since_epoch,
|
131
|
-
timedelta_to_microseconds,
|
132
|
-
timedelta_to_milliseconds,
|
133
133
|
yield_days,
|
134
134
|
yield_weekdays,
|
135
135
|
)
|
@@ -520,6 +520,60 @@ class TestDateTimeDurationToFloat:
|
|
520
520
|
assert result == timedelta.total_seconds()
|
521
521
|
|
522
522
|
|
523
|
+
class TestDateTimeDurationToMicrosecondsOrMilliseconds:
|
524
|
+
@given(timedelta=timedeltas())
|
525
|
+
def test_timedelta_to_microseconds(self, *, timedelta: dt.timedelta) -> None:
|
526
|
+
microseconds = datetime_duration_to_microseconds(timedelta)
|
527
|
+
result = microseconds_to_timedelta(microseconds)
|
528
|
+
assert result == timedelta
|
529
|
+
|
530
|
+
@given(microseconds=integers())
|
531
|
+
def test_microseconds_to_timedelta(self, *, microseconds: int) -> None:
|
532
|
+
with assume_does_not_raise(OverflowError):
|
533
|
+
timedelta = microseconds_to_timedelta(microseconds)
|
534
|
+
result = datetime_duration_to_microseconds(timedelta)
|
535
|
+
assert result == microseconds
|
536
|
+
|
537
|
+
@given(timedelta=timedeltas(), strict=booleans())
|
538
|
+
@settings(suppress_health_check={HealthCheck.filter_too_much})
|
539
|
+
def test_timedelta_to_milliseconds_exact(
|
540
|
+
self, *, timedelta: dt.timedelta, strict: bool
|
541
|
+
) -> None:
|
542
|
+
_, remainder = divmod(timedelta.microseconds, _MICROSECONDS_PER_MILLISECOND)
|
543
|
+
_ = assume(remainder == 0)
|
544
|
+
milliseconds = datetime_duration_to_milliseconds(timedelta, strict=strict)
|
545
|
+
assert isinstance(milliseconds, int)
|
546
|
+
result = milliseconds_to_timedelta(milliseconds)
|
547
|
+
assert result == timedelta
|
548
|
+
|
549
|
+
@given(timedelta=timedeltas())
|
550
|
+
def test_timedelta_to_milliseconds_inexact(
|
551
|
+
self, *, timedelta: dt.timedelta
|
552
|
+
) -> None:
|
553
|
+
_, remainder = divmod(timedelta.microseconds, _MICROSECONDS_PER_MILLISECOND)
|
554
|
+
_ = assume(remainder != 0)
|
555
|
+
milliseconds = datetime_duration_to_milliseconds(timedelta)
|
556
|
+
result = milliseconds_to_timedelta(round(milliseconds))
|
557
|
+
assert abs(result - timedelta) <= SECOND
|
558
|
+
|
559
|
+
@given(timedelta=timedeltas())
|
560
|
+
def test_timedelta_to_milliseconds_error(self, *, timedelta: dt.timedelta) -> None:
|
561
|
+
_, microseconds = divmod(timedelta.microseconds, _MICROSECONDS_PER_MILLISECOND)
|
562
|
+
_ = assume(microseconds != 0)
|
563
|
+
with raises(
|
564
|
+
TimedeltaToMillisecondsError,
|
565
|
+
match=r"Unable to convert .* to milliseconds; got .* microsecond\(s\)",
|
566
|
+
):
|
567
|
+
_ = datetime_duration_to_milliseconds(timedelta, strict=True)
|
568
|
+
|
569
|
+
@given(milliseconds=int32s())
|
570
|
+
def test_milliseconds_to_timedelta(self, *, milliseconds: int) -> None:
|
571
|
+
with assume_does_not_raise(OverflowError):
|
572
|
+
timedelta = milliseconds_to_timedelta(milliseconds)
|
573
|
+
result = datetime_duration_to_milliseconds(timedelta)
|
574
|
+
assert result == milliseconds
|
575
|
+
|
576
|
+
|
523
577
|
class TestDateTimeDurationToTimeDelta:
|
524
578
|
@given(n=int32s())
|
525
579
|
def test_int(self, *, n: int) -> None:
|
@@ -1205,60 +1259,6 @@ class TestTimedeltaSinceEpoch:
|
|
1205
1259
|
assert result1 == result2
|
1206
1260
|
|
1207
1261
|
|
1208
|
-
class TestTimedeltaToMicrosecondsOrMilliseconds:
|
1209
|
-
@given(timedelta=timedeltas())
|
1210
|
-
def test_timedelta_to_microseconds(self, *, timedelta: dt.timedelta) -> None:
|
1211
|
-
microseconds = timedelta_to_microseconds(timedelta)
|
1212
|
-
result = microseconds_to_timedelta(microseconds)
|
1213
|
-
assert result == timedelta
|
1214
|
-
|
1215
|
-
@given(microseconds=integers())
|
1216
|
-
def test_microseconds_to_timedelta(self, *, microseconds: int) -> None:
|
1217
|
-
with assume_does_not_raise(OverflowError):
|
1218
|
-
timedelta = microseconds_to_timedelta(microseconds)
|
1219
|
-
result = timedelta_to_microseconds(timedelta)
|
1220
|
-
assert result == microseconds
|
1221
|
-
|
1222
|
-
@given(timedelta=timedeltas(), strict=booleans())
|
1223
|
-
@settings(suppress_health_check={HealthCheck.filter_too_much})
|
1224
|
-
def test_timedelta_to_milliseconds_exact(
|
1225
|
-
self, *, timedelta: dt.timedelta, strict: bool
|
1226
|
-
) -> None:
|
1227
|
-
_, remainder = divmod(timedelta.microseconds, _MICROSECONDS_PER_MILLISECOND)
|
1228
|
-
_ = assume(remainder == 0)
|
1229
|
-
milliseconds = timedelta_to_milliseconds(timedelta, strict=strict)
|
1230
|
-
assert isinstance(milliseconds, int)
|
1231
|
-
result = milliseconds_to_timedelta(milliseconds)
|
1232
|
-
assert result == timedelta
|
1233
|
-
|
1234
|
-
@given(timedelta=timedeltas())
|
1235
|
-
def test_timedelta_to_milliseconds_inexact(
|
1236
|
-
self, *, timedelta: dt.timedelta
|
1237
|
-
) -> None:
|
1238
|
-
_, remainder = divmod(timedelta.microseconds, _MICROSECONDS_PER_MILLISECOND)
|
1239
|
-
_ = assume(remainder != 0)
|
1240
|
-
milliseconds = timedelta_to_milliseconds(timedelta)
|
1241
|
-
result = milliseconds_to_timedelta(round(milliseconds))
|
1242
|
-
assert abs(result - timedelta) <= SECOND
|
1243
|
-
|
1244
|
-
@given(timedelta=timedeltas())
|
1245
|
-
def test_timedelta_to_milliseconds_error(self, *, timedelta: dt.timedelta) -> None:
|
1246
|
-
_, microseconds = divmod(timedelta.microseconds, _MICROSECONDS_PER_MILLISECOND)
|
1247
|
-
_ = assume(microseconds != 0)
|
1248
|
-
with raises(
|
1249
|
-
TimedeltaToMillisecondsError,
|
1250
|
-
match=r"Unable to convert .* to milliseconds; got .* microsecond\(s\)",
|
1251
|
-
):
|
1252
|
-
_ = timedelta_to_milliseconds(timedelta, strict=True)
|
1253
|
-
|
1254
|
-
@given(milliseconds=int32s())
|
1255
|
-
def test_milliseconds_to_timedelta(self, *, milliseconds: int) -> None:
|
1256
|
-
with assume_does_not_raise(OverflowError):
|
1257
|
-
timedelta = milliseconds_to_timedelta(milliseconds)
|
1258
|
-
result = timedelta_to_milliseconds(timedelta)
|
1259
|
-
assert result == milliseconds
|
1260
|
-
|
1261
|
-
|
1262
1262
|
class TestTimedeltas:
|
1263
1263
|
@mark.parametrize(
|
1264
1264
|
"timedelta",
|
@@ -1,5 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
import datetime as dt
|
3
4
|
from abc import ABC, abstractmethod
|
4
5
|
from asyncio import (
|
5
6
|
CancelledError,
|
@@ -32,6 +33,7 @@ from typing import (
|
|
32
33
|
TYPE_CHECKING,
|
33
34
|
Any,
|
34
35
|
Generic,
|
36
|
+
Literal,
|
35
37
|
NoReturn,
|
36
38
|
Self,
|
37
39
|
TextIO,
|
@@ -41,7 +43,15 @@ from typing import (
|
|
41
43
|
override,
|
42
44
|
)
|
43
45
|
|
44
|
-
from utilities.datetime import
|
46
|
+
from utilities.datetime import (
|
47
|
+
MILLISECOND,
|
48
|
+
MINUTE,
|
49
|
+
SECOND,
|
50
|
+
datetime_duration_to_float,
|
51
|
+
datetime_duration_to_timedelta,
|
52
|
+
get_now,
|
53
|
+
round_datetime,
|
54
|
+
)
|
45
55
|
from utilities.errors import ImpossibleCaseError, repr_error
|
46
56
|
from utilities.functions import ensure_int, ensure_not_none, get_class_name
|
47
57
|
from utilities.reprlib import get_repr
|
@@ -329,12 +339,15 @@ class ExceptionProcessor(QueueProcessor[Exception | type[Exception]]):
|
|
329
339
|
##
|
330
340
|
|
331
341
|
|
342
|
+
type _DurationOrEvery = Duration | tuple[Literal["every"], Duration]
|
343
|
+
|
344
|
+
|
332
345
|
@dataclass(kw_only=True, unsafe_hash=True)
|
333
346
|
class InfiniteLooper(ABC, Generic[THashable]):
|
334
347
|
"""An infinite loop which can throw exceptions by setting events."""
|
335
348
|
|
336
|
-
sleep_core:
|
337
|
-
sleep_restart:
|
349
|
+
sleep_core: _DurationOrEvery = SECOND
|
350
|
+
sleep_restart: _DurationOrEvery = MINUTE
|
338
351
|
logger: str | None = None
|
339
352
|
_events: Mapping[THashable, Event] = field(
|
340
353
|
default_factory=dict, init=False, repr=False, hash=False
|
@@ -361,7 +374,7 @@ class InfiniteLooper(ABC, Generic[THashable]):
|
|
361
374
|
await self._initialize()
|
362
375
|
except Exception as error: # noqa: BLE001
|
363
376
|
self._error_upon_initialize(error)
|
364
|
-
await
|
377
|
+
await self._run_sleep(self.sleep_restart)
|
365
378
|
else:
|
366
379
|
while True:
|
367
380
|
try:
|
@@ -372,14 +385,14 @@ class InfiniteLooper(ABC, Generic[THashable]):
|
|
372
385
|
)
|
373
386
|
except StopIteration:
|
374
387
|
await self._core()
|
375
|
-
await
|
388
|
+
await self._run_sleep(self.sleep_core)
|
376
389
|
else:
|
377
390
|
self._raise_error(event)
|
378
391
|
except InfiniteLooperError:
|
379
392
|
raise
|
380
393
|
except Exception as error: # noqa: BLE001
|
381
394
|
self._error_upon_core(error)
|
382
|
-
await
|
395
|
+
await self._run_sleep(self.sleep_restart)
|
383
396
|
|
384
397
|
async def _run_looper_with_coroutines(
|
385
398
|
self, *coroutines: Callable[[], Coroutine1[None]]
|
@@ -393,7 +406,7 @@ class InfiniteLooper(ABC, Generic[THashable]):
|
|
393
406
|
_ = [tg.create_task(c()) for c in coroutines]
|
394
407
|
except ExceptionGroup as error:
|
395
408
|
self._error_group_upon_coroutines(error)
|
396
|
-
await
|
409
|
+
await self._run_sleep(self.sleep_restart)
|
397
410
|
|
398
411
|
async def _initialize(self) -> None:
|
399
412
|
"""Initialize the loop."""
|
@@ -405,20 +418,20 @@ class InfiniteLooper(ABC, Generic[THashable]):
|
|
405
418
|
"""Handle any errors upon initializing the looper."""
|
406
419
|
if self.logger is not None:
|
407
420
|
getLogger(name=self.logger).error(
|
408
|
-
"%r encountered %r whilst initializing; sleeping
|
421
|
+
"%r encountered %r whilst initializing; sleeping %s...",
|
409
422
|
get_class_name(self),
|
410
423
|
repr_error(error),
|
411
|
-
self.
|
424
|
+
self._sleep_restart_desc,
|
412
425
|
)
|
413
426
|
|
414
427
|
def _error_upon_core(self, error: Exception, /) -> None:
|
415
428
|
"""Handle any errors upon running the core function."""
|
416
429
|
if self.logger is not None:
|
417
430
|
getLogger(name=self.logger).error(
|
418
|
-
"%r encountered %r; sleeping
|
431
|
+
"%r encountered %r; sleeping %s...",
|
419
432
|
get_class_name(self),
|
420
433
|
repr_error(error),
|
421
|
-
self.
|
434
|
+
self._sleep_restart_desc,
|
422
435
|
)
|
423
436
|
|
424
437
|
def _error_group_upon_coroutines(self, group: ExceptionGroup, /) -> None:
|
@@ -431,7 +444,7 @@ class InfiniteLooper(ABC, Generic[THashable]):
|
|
431
444
|
f"- Error #{i}/{n}: {repr_error(e)}"
|
432
445
|
for i, e in enumerate(errors, start=1)
|
433
446
|
)
|
434
|
-
msgs.append(f"Sleeping
|
447
|
+
msgs.append(f"Sleeping {self._sleep_restart_desc}...")
|
435
448
|
getLogger(name=self.logger).error("\n".join(msgs))
|
436
449
|
|
437
450
|
def _raise_error(self, event: THashable, /) -> NoReturn:
|
@@ -446,6 +459,29 @@ class InfiniteLooper(ABC, Generic[THashable]):
|
|
446
459
|
event: Event() for event, _ in self._yield_events_and_exceptions()
|
447
460
|
}
|
448
461
|
|
462
|
+
async def _run_sleep(self, sleep: _DurationOrEvery, /) -> None:
|
463
|
+
"""Sleep until the next part of the loop."""
|
464
|
+
match sleep:
|
465
|
+
case int() | float() | dt.timedelta() as duration:
|
466
|
+
await sleep_dur(duration=duration)
|
467
|
+
case "every", (int() | float() | dt.timedelta()) as duration:
|
468
|
+
await sleep_until_rounded(duration)
|
469
|
+
case _ as never:
|
470
|
+
assert_never(never)
|
471
|
+
|
472
|
+
@property
|
473
|
+
def _sleep_restart_desc(self) -> str:
|
474
|
+
"""Get a description of the sleep until restart."""
|
475
|
+
match self.sleep_restart:
|
476
|
+
case int() | float() | dt.timedelta() as duration:
|
477
|
+
timedelta = datetime_duration_to_timedelta(duration)
|
478
|
+
return f"for {timedelta}"
|
479
|
+
case "every", (int() | float() | dt.timedelta()) as duration:
|
480
|
+
timedelta = datetime_duration_to_timedelta(duration)
|
481
|
+
return f"until next {timedelta}"
|
482
|
+
case _ as never:
|
483
|
+
assert_never(never)
|
484
|
+
|
449
485
|
def _set_event(self, event: THashable, /) -> None:
|
450
486
|
"""Set the given event."""
|
451
487
|
try:
|
@@ -686,6 +722,27 @@ async def sleep_dur(*, duration: Duration | None = None) -> None:
|
|
686
722
|
##
|
687
723
|
|
688
724
|
|
725
|
+
async def sleep_until(datetime: dt.datetime, /) -> None:
|
726
|
+
"""Sleep until a given time."""
|
727
|
+
await sleep_dur(duration=datetime - get_now())
|
728
|
+
|
729
|
+
|
730
|
+
##
|
731
|
+
|
732
|
+
|
733
|
+
async def sleep_until_rounded(
|
734
|
+
duration: Duration, /, *, rel_tol: float | None = None, abs_tol: float | None = None
|
735
|
+
) -> None:
|
736
|
+
"""Sleep until a rounded time; accepts durations."""
|
737
|
+
datetime = round_datetime(
|
738
|
+
get_now(), duration, mode="ceil", rel_tol=rel_tol, abs_tol=abs_tol
|
739
|
+
)
|
740
|
+
await sleep_until(datetime)
|
741
|
+
|
742
|
+
|
743
|
+
##
|
744
|
+
|
745
|
+
|
689
746
|
@dataclass(kw_only=True, slots=True)
|
690
747
|
class StreamCommandOutput:
|
691
748
|
process: Process
|
@@ -768,6 +825,8 @@ __all__ = [
|
|
768
825
|
"put_items",
|
769
826
|
"put_items_nowait",
|
770
827
|
"sleep_dur",
|
828
|
+
"sleep_until",
|
829
|
+
"sleep_until_rounded",
|
771
830
|
"stream_command",
|
772
831
|
"timeout_dur",
|
773
832
|
]
|